2 * ========================LICENSE_START=================================
5 * Copyright (C) 2019 AT&T Intellectual Property
6 * Modifications Copyright (C) 2020 Nordix Foundation
8 * Licensed under the Apache License, Version 2.0 (the "License");
9 * you may not use this file except in compliance with the License.
10 * You may obtain a copy of the License at
12 * http://www.apache.org/licenses/LICENSE-2.0
14 * Unless required by applicable law or agreed to in writing, software
15 * distributed under the License is distributed on an "AS IS" BASIS,
16 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 * See the License for the specific language governing permissions and
18 * limitations under the License.
19 * ========================LICENSE_END===================================
21 package org.oransc.portal.nonrtric.controlpanel.portalapi;
23 import java.io.IOException;
24 import java.io.InputStream;
25 import java.io.UnsupportedEncodingException;
26 import java.lang.invoke.MethodHandles;
27 import java.net.URLEncoder;
28 import java.util.HashSet;
30 import javax.servlet.Filter;
31 import javax.servlet.FilterChain;
32 import javax.servlet.FilterConfig;
33 import javax.servlet.ServletException;
34 import javax.servlet.ServletRequest;
35 import javax.servlet.ServletResponse;
36 import javax.servlet.http.Cookie;
37 import javax.servlet.http.HttpServletRequest;
38 import javax.servlet.http.HttpServletResponse;
40 import org.onap.portalsdk.core.onboarding.util.KeyProperties;
41 import org.onap.portalsdk.core.onboarding.util.PortalApiConstants;
42 import org.onap.portalsdk.core.onboarding.util.PortalApiProperties;
43 import org.onap.portalsdk.core.restful.domain.EcompRole;
44 import org.onap.portalsdk.core.restful.domain.EcompUser;
45 import org.oransc.portal.nonrtric.controlpanel.ControlpanelConstants;
46 import org.oransc.portal.nonrtric.controlpanel.ControlpanelUserManager;
47 import org.oransc.portal.nonrtric.controlpanel.model.EcompUserDetails;
48 import org.owasp.esapi.reference.DefaultSecurityConfiguration;
49 import org.slf4j.Logger;
50 import org.slf4j.LoggerFactory;
51 import org.springframework.http.MediaType;
52 import org.springframework.security.core.Authentication;
53 import org.springframework.security.core.context.SecurityContextHolder;
54 import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
57 * This filter checks every request for the cookie set by the ONAP Portal single
58 * sign on process. The possible paths and actions:
60 * <LI>User starts at an app page via a bookmark. No Portal cookie is set.
61 * Redirect there to get one; then continue as below.
62 * <LI>User starts at Portal and goes to app. Alternately, the user's session
63 * times out and the user hits refresh. The Portal cookie is set, but there is
64 * no valid session. Create one and publish info.
65 * <LI>User has valid Portal cookie and session. Reset the max idle in that
71 * <LI>While redirecting, the cookie "redirectUrl" should also be set so that
72 * Portal knows where to forward the request to once the Portal Session is
73 * created and EPService cookie is set.
76 * Open question: What about sessions? Will this be stateless?
78 * This filter uses no annotations to avoid Spring's automatic registration,
79 * which add this filter in the chain in the wrong order.
81 public class PortalAuthenticationFilter implements Filter {
83 private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
85 // Unfortunately not all file names are defined as constants
86 private static final String[] securityPropertyFiles =
87 {KeyProperties.PROPERTY_FILE_NAME, PortalApiProperties.PROPERTY_FILE_NAME,
88 DefaultSecurityConfiguration.DEFAULT_RESOURCE_FILE, "validation.properties"};
90 public static final String REDIRECT_URL_KEY = "redirectUrl";
92 private final boolean enforcePortalSecurity;
93 private final PortalAuthManager authManager;
95 private final ControlpanelUserManager userManager;
97 public PortalAuthenticationFilter(boolean portalSecurity, PortalAuthManager authManager,
98 ControlpanelUserManager userManager) throws IOException {
99 this.enforcePortalSecurity = portalSecurity;
100 this.authManager = authManager;
101 this.userManager = userManager;
102 if (portalSecurity) {
103 // Throw if security is requested and prerequisites are not met
104 for (String pf : securityPropertyFiles) {
105 InputStream in = MethodHandles.lookup().lookupClass().getClassLoader().getResourceAsStream(pf);
107 String msg = "Failed to find property file on classpath: " + pf;
109 throw new IOException(msg);
113 } catch (IOException ex) {
114 logger.warn("Failed to close stream", ex);
122 public void init(FilterConfig filterConfig) throws ServletException {
123 // complain loudly if this key property is missing
124 String url = PortalApiProperties.getProperty(PortalApiConstants.ECOMP_REDIRECT_URL);
125 logger.debug("init: Portal redirect URL {}", url);
128 .error("init: Failed to find property in portal.properties: " + PortalApiConstants.ECOMP_REDIRECT_URL);
132 public void destroy() {
133 // No resources to release
137 * Requests for pages ignored in the web security config do not hit this filter.
140 public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
141 throws IOException, ServletException {
142 if (enforcePortalSecurity)
143 doFilterEPSDKFW(req, res, chain);
145 doFilterMockUserAdminRole(req, res, chain);
149 * Populates security context with a mock user in the admin role.
152 private void doFilterMockUserAdminRole(ServletRequest req, ServletResponse res, FilterChain chain)
153 throws IOException, ServletException {
154 Authentication auth = SecurityContextHolder.getContext().getAuthentication();
155 if (auth == null || auth.getAuthorities().isEmpty()) {
156 if (logger.isDebugEnabled()) {
157 logger.debug("doFilter adding auth to request URI {}",
158 (req instanceof HttpServletRequest) ? ((HttpServletRequest) req).getRequestURL() : req);
160 EcompRole admin = new EcompRole();
162 admin.setName(ControlpanelConstants.ROLE_ADMIN);
163 HashSet<EcompRole> roles = new HashSet<>();
165 EcompUser user = new EcompUser();
166 user.setLoginId("fakeLoginId");
167 user.setRoles(roles);
168 user.setActive(true);
169 EcompUserDetails userDetails = new EcompUserDetails(user);
170 PreAuthenticatedAuthenticationToken authToken =
171 new PreAuthenticatedAuthenticationToken(userDetails, "fakeCredentials", userDetails.getAuthorities());
172 SecurityContextHolder.getContext().setAuthentication(authToken);
174 logger.debug("doFilter: authorities {}", auth.getAuthorities());
176 chain.doFilter(req, res);
180 * Checks for valid cookies and allows request to be served if found; redirects
181 * to Portal otherwise.
183 private void doFilterEPSDKFW(ServletRequest req, ServletResponse res, FilterChain chain)
184 throws IOException, ServletException {
185 HttpServletRequest request = (HttpServletRequest) req;
186 HttpServletResponse response = (HttpServletResponse) res;
187 if (logger.isTraceEnabled())
188 logger.trace("doFilter: req {}", request.getRequestURI());
189 // Need to authenticate the request
190 final String userId = authManager.validateEcompSso(request);
191 final EcompUser ecompUser = (userId == null ? null : userManager.getUser(userId));
192 if (userId == null || ecompUser == null) {
193 logger.debug("doFilter: unauthorized user requests URI {}, serving login page", request.getRequestURI());
194 StringBuffer sb = request.getRequestURL();
195 sb.append(request.getQueryString() == null ? "" : "?" + request.getQueryString());
196 String body = generateLoginRedirectPage(sb.toString());
197 response.setContentType(MediaType.TEXT_HTML_VALUE);
198 response.getWriter().print(body);
199 response.getWriter().flush();
201 EcompUserDetails userDetails = new EcompUserDetails(ecompUser);
202 // Using portal session as credentials is a hack
203 PreAuthenticatedAuthenticationToken authToken = new PreAuthenticatedAuthenticationToken(userDetails,
204 getPortalSessionId(request), userDetails.getAuthorities());
205 SecurityContextHolder.getContext().setAuthentication(authToken);
206 // Pass request back down the filter chain
207 chain.doFilter(request, response);
212 * Generates a page with text only, absolutely no references to any webapp
213 * resources, so this can be served to an unauthenticated user without
214 * triggering a new authentication attempt. The page has a link to the Portal
215 * URL from configuration, with a return URL that is the original request.
218 * Original requested URL
220 * @throws UnsupportedEncodingException
223 private static String generateLoginRedirectPage(String appUrl) throws UnsupportedEncodingException {
224 String encodedAppUrl = URLEncoder.encode(appUrl, "UTF-8");
225 String portalBaseUrl = PortalApiProperties.getProperty(PortalApiConstants.ECOMP_REDIRECT_URL);
226 String redirectUrl = portalBaseUrl + "?" + PortalAuthenticationFilter.REDIRECT_URL_KEY + "=" + encodedAppUrl;
227 String aHref = "<a href=\"" + redirectUrl + "\">";
228 // If only Java had "here" documents.
229 return String.join(//
230 System.getProperty("line.separator"), //
233 "<title>Non-RT RIC Controlpanel</title>", //
236 " font-family: Helvetica, Arial, sans-serif;", //
241 "<h2>Non-RT RIC Controlpanel</h2>", //
242 "<h4>Please log in.</h4>", //
244 aHref, "Click here to authenticate at the ONAP Portal</a>", //
251 * Searches the request for a cookie with the specified name.
257 * @return Cookie, or null if not found.
259 private Cookie getCookie(HttpServletRequest request, String cookieName) {
260 Cookie[] cookies = request.getCookies();
262 for (Cookie cookie : cookies)
263 if (cookie.getName().equals(cookieName))
269 * Gets the ECOMP Portal service cookie value.
272 * @return Cookie value, or null if not found.
274 private String getPortalSessionId(HttpServletRequest request) {
275 Cookie ep = getCookie(request, PortalApiConstants.EP_SERVICE);
278 return ep.getValue();