Release dashboard image at version 2.1.0
[portal/ric-dashboard.git] / dashboard / webapp-backend / src / main / java / org / oransc / ric / portal / dashboard / portalapi / PortalAuthenticationFilter.java
1 /*-
2  * ========================LICENSE_START=================================
3  * O-RAN-SC
4  * %%
5  * Copyright (C) 2019 AT&T Intellectual Property
6  * %%
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  * 
11  *      http://www.apache.org/licenses/LICENSE-2.0
12  * 
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  * ========================LICENSE_END===================================
19  */
20 package org.oransc.ric.portal.dashboard.portalapi;
21
22 import java.io.IOException;
23 import java.io.InputStream;
24 import java.io.UnsupportedEncodingException;
25 import java.lang.invoke.MethodHandles;
26 import java.net.URLEncoder;
27 import java.util.HashSet;
28
29 import javax.servlet.Filter;
30 import javax.servlet.FilterChain;
31 import javax.servlet.FilterConfig;
32 import javax.servlet.ServletException;
33 import javax.servlet.ServletRequest;
34 import javax.servlet.ServletResponse;
35 import javax.servlet.http.Cookie;
36 import javax.servlet.http.HttpServletRequest;
37 import javax.servlet.http.HttpServletResponse;
38
39 import org.onap.portalsdk.core.onboarding.util.KeyProperties;
40 import org.onap.portalsdk.core.onboarding.util.PortalApiConstants;
41 import org.onap.portalsdk.core.onboarding.util.PortalApiProperties;
42 import org.onap.portalsdk.core.restful.domain.EcompRole;
43 import org.onap.portalsdk.core.restful.domain.EcompUser;
44 import org.oransc.ric.portal.dashboard.DashboardConstants;
45 import org.oransc.ric.portal.dashboard.DashboardUserManager;
46 import org.oransc.ric.portal.dashboard.model.EcompUserDetails;
47 import org.owasp.esapi.reference.DefaultSecurityConfiguration;
48 import org.slf4j.Logger;
49 import org.slf4j.LoggerFactory;
50 import org.springframework.http.MediaType;
51 import org.springframework.security.core.Authentication;
52 import org.springframework.security.core.context.SecurityContextHolder;
53 import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
54
55 /**
56  * This filter checks every request for the cookie set by the ONAP Portal single
57  * sign on process. The possible paths and actions:
58  * <OL>
59  * <LI>User starts at an app page via a bookmark. No Portal cookie is set.
60  * Redirect there to get one; then continue as below.
61  * <LI>User starts at Portal and goes to app. Alternately, the user's session
62  * times out and the user hits refresh. The Portal cookie is set, but there is
63  * no valid session. Create one and publish info.
64  * <LI>User has valid Portal cookie and session. Reset the max idle in that
65  * session.
66  * </OL>
67  * <P>
68  * Notes:
69  * <UL>
70  * <LI>While redirecting, the cookie "redirectUrl" should also be set so that
71  * Portal knows where to forward the request to once the Portal Session is
72  * created and EPService cookie is set.
73  * </UL>
74  * 
75  * Open question: what about sessions? Will this be stateless?
76  * 
77  * This filter uses no annotations to avoid Spring's automatic registration,
78  * which add this filter in the chain in the wrong order.
79  */
80 public class PortalAuthenticationFilter implements Filter {
81
82         private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
83
84         // Unfortunately not all file names are defined as constants
85         private static final String[] securityPropertyFiles = { KeyProperties.PROPERTY_FILE_NAME,
86                         PortalApiProperties.PROPERTY_FILE_NAME, DefaultSecurityConfiguration.DEFAULT_RESOURCE_FILE,
87                         "validation.properties" };
88
89         public static final String REDIRECT_URL_KEY = "redirectUrl";
90
91         private final boolean enforcePortalSecurity;
92         private final PortalAuthManager authManager;
93
94         private final DashboardUserManager userManager;
95
96         public PortalAuthenticationFilter(boolean portalSecurity, PortalAuthManager authManager,
97                         DashboardUserManager userManager) {
98                 this.enforcePortalSecurity = portalSecurity;
99                 this.authManager = authManager;
100                 this.userManager = userManager;
101                 if (portalSecurity) {
102                         // Throw if security is requested and prerequisites are not met
103                         for (String pf : securityPropertyFiles) {
104                                 InputStream in = MethodHandles.lookup().lookupClass().getClassLoader().getResourceAsStream(pf);
105                                 if (in == null) {
106                                         String msg = "Failed to find property file on classpath: " + pf;
107                                         logger.error(msg);
108                                         throw new SecurityException(msg);
109                                 } else {
110                                         try {
111                                                 in.close();
112                                         } catch (IOException ex) {
113                                                 logger.warn("Failed to close stream", ex);
114                                         }
115                                 }
116                         }
117                 }
118         }
119
120         @Override
121         public void init(FilterConfig filterConfig) throws ServletException {
122                 // complain loudly if this key property is missing
123                 String url = PortalApiProperties.getProperty(PortalApiConstants.ECOMP_REDIRECT_URL);
124                 logger.debug("init: Portal redirect URL {}", url);
125                 if (url == null)
126                         logger.error(
127                                         "init: Failed to find property in portal.properties: " + PortalApiConstants.ECOMP_REDIRECT_URL);
128         }
129
130         @Override
131         public void destroy() {
132                 // No resources to release
133         }
134
135         /**
136          * Requests for pages ignored in the web security config do not hit this filter.
137          */
138         @Override
139         public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
140                         throws IOException, ServletException {
141                 if (enforcePortalSecurity)
142                         doFilterEPSDKFW(req, res, chain);
143                 else
144                         doFilterMockUserAdminRole(req, res, chain);
145         }
146
147         /*
148          * Populates security context with a mock user in the admin role.
149          * 
150          */
151         private void doFilterMockUserAdminRole(ServletRequest req, ServletResponse res, FilterChain chain)
152                         throws IOException, ServletException {
153                 Authentication auth = SecurityContextHolder.getContext().getAuthentication();
154                 if (auth == null || auth.getAuthorities().isEmpty()) {
155                         if (logger.isDebugEnabled()) {
156                                 logger.debug("doFilter adding auth to request URI {}",
157                                                 (req instanceof HttpServletRequest) ? ((HttpServletRequest) req).getRequestURL() : req);
158                         }
159                         EcompRole admin = new EcompRole();
160                         admin.setId(1L);
161                         admin.setName(DashboardConstants.ROLE_ADMIN);
162                         HashSet<EcompRole> roles = new HashSet<>();
163                         roles.add(admin);
164                         EcompUser user = new EcompUser();
165                         user.setLoginId("fakeLoginId");
166                         user.setRoles(roles);
167                         user.setActive(true);
168                         EcompUserDetails userDetails = new EcompUserDetails(user);
169                         PreAuthenticatedAuthenticationToken authToken = new PreAuthenticatedAuthenticationToken(userDetails,
170                                         "fakeCredentials", userDetails.getAuthorities());
171                         SecurityContextHolder.getContext().setAuthentication(authToken);
172                 } else {
173                         logger.debug("doFilter: authorities {}", auth.getAuthorities());
174                 }
175                 chain.doFilter(req, res);
176         }
177
178         /*
179          * Checks for valid cookies and allows request to be served if found; redirects
180          * to Portal otherwise.
181          */
182         private void doFilterEPSDKFW(ServletRequest req, ServletResponse res, FilterChain chain)
183                         throws IOException, ServletException {
184                 HttpServletRequest request = (HttpServletRequest) req;
185                 HttpServletResponse response = (HttpServletResponse) res;
186                 if (logger.isTraceEnabled())
187                         logger.trace("doFilter: req {}", request.getRequestURI());
188                 // Need to authenticate the request
189                 final String userId = authManager.validateEcompSso(request);
190                 final EcompUser ecompUser = (userId == null ? null : userManager.getUser(userId));
191                 if (userId == null || ecompUser == null) {
192                         logger.debug("doFilter: unauthorized user requests URI {}, serving login page", request.getRequestURI());
193                         StringBuffer sb = request.getRequestURL();
194                         sb.append(request.getQueryString() == null ? "" : "?" + request.getQueryString());
195                         String body = generateLoginRedirectPage(sb.toString());
196                         response.setContentType(MediaType.TEXT_HTML_VALUE);
197                         response.getWriter().print(body);
198                         response.getWriter().flush();
199                 } else {
200                         EcompUserDetails userDetails = new EcompUserDetails(ecompUser);
201                         // Using portal session as credentials is a hack
202                         PreAuthenticatedAuthenticationToken authToken = new PreAuthenticatedAuthenticationToken(userDetails,
203                                         getPortalSessionId(request), userDetails.getAuthorities());
204                         SecurityContextHolder.getContext().setAuthentication(authToken);
205                         // Pass request back down the filter chain
206                         chain.doFilter(request, response);
207                 }
208         }
209
210         /**
211          * Generates a page with text only, absolutely no references to any webapp
212          * resources, so this can be served to an unauthenticated user without
213          * triggering a new authentication attempt. The page has a link to the Portal
214          * URL from configuration, with a return URL that is the original request.
215          * 
216          * @param appUrl
217          *                   Original requested URL
218          * @return HTML
219          * @throws UnsupportedEncodingException
220          *                                          On error
221          */
222         private static String generateLoginRedirectPage(String appUrl) throws UnsupportedEncodingException {
223                 String encodedAppUrl = URLEncoder.encode(appUrl, "UTF-8");
224                 String portalBaseUrl = PortalApiProperties.getProperty(PortalApiConstants.ECOMP_REDIRECT_URL);
225                 String redirectUrl = portalBaseUrl + "?" + PortalAuthenticationFilter.REDIRECT_URL_KEY + "=" + encodedAppUrl;
226                 String aHref = "<a href=\"" + redirectUrl + "\">";
227                 // If only Java had "here" documents.
228                 return String.join(//
229                                 System.getProperty("line.separator"), //
230                                 "<html>", //
231                                 "<head>", //
232                                 "<title>RIC Dashboard</title>", //
233                                 "<style>", //
234                                 "html, body { ", //
235                                 "  font-family: Helvetica, Arial, sans-serif;", //
236                                 "}", //
237                                 "</style>", //
238                                 "</head>", //
239                                 "<body>", //
240                                 "<h2>RIC Dashboard</h2>", //
241                                 "<h4>Please log in.</h4>", //
242                                 "<p>", //
243                                 aHref, "Click here to authenticate at the ONAP Portal</a>", //
244                                 "</p>", //
245                                 "</body>", //
246                                 "</html>");
247         }
248
249         /**
250          * Searches the request for a cookie with the specified name.
251          *
252          * @param request
253          *                       HttpServletRequest
254          * @param cookieName
255          *                       Cookie name
256          * @return Cookie, or null if not found.
257          */
258         private Cookie getCookie(HttpServletRequest request, String cookieName) {
259                 Cookie[] cookies = request.getCookies();
260                 if (cookies != null)
261                         for (Cookie cookie : cookies)
262                                 if (cookie.getName().equals(cookieName))
263                                         return cookie;
264                 return null;
265         }
266
267         /**
268          * Gets the ECOMP Portal service cookie value.
269          * 
270          * @param request
271          * @return Cookie value, or null if not found.
272          */
273         private String getPortalSessionId(HttpServletRequest request) {
274                 Cookie ep = getCookie(request, PortalApiConstants.EP_SERVICE);
275                 if (ep == null)
276                         return null;
277                 return ep.getValue();
278         }
279
280 }