Fix formatting in the dashboard
[nonrtric.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  * TODO: 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 =
86         {KeyProperties.PROPERTY_FILE_NAME, PortalApiProperties.PROPERTY_FILE_NAME,
87             DefaultSecurityConfiguration.DEFAULT_RESOURCE_FILE, "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 RuntimeException(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
127                 .error("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 =
170                 new PreAuthenticatedAuthenticationToken(userDetails, "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         String body = 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         return body;
248     }
249
250     /**
251      * Searches the request for a cookie with the specified name.
252      *
253      * @param request
254      *        HttpServletRequest
255      * @param cookieName
256      *        Cookie name
257      * @return Cookie, or null if not found.
258      */
259     private Cookie getCookie(HttpServletRequest request, String cookieName) {
260         Cookie[] cookies = request.getCookies();
261         if (cookies != null)
262             for (Cookie cookie : cookies)
263                 if (cookie.getName().equals(cookieName))
264                     return cookie;
265         return null;
266     }
267
268     /**
269      * Gets the ECOMP Portal service cookie value.
270      *
271      * @param request
272      * @return Cookie value, or null if not found.
273      */
274     private String getPortalSessionId(HttpServletRequest request) {
275         Cookie ep = getCookie(request, PortalApiConstants.EP_SERVICE);
276         if (ep == null)
277             return null;
278         return ep.getValue();
279     }
280
281 }