First version of NonRT RIC Controlpanel
[portal/nonrtric-controlpanel.git] / webapp-backend / src / main / java / org / oransc / portal / nonrtric / controlpanel / portalapi / PortalAuthenticationFilter.java
1 /*-
2  * ========================LICENSE_START=================================
3  * O-RAN-SC
4  * %%
5  * Copyright (C) 2019 AT&T Intellectual Property
6  * Modifications Copyright (C) 2020 Nordix Foundation
7  * %%
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
11  *
12  *      http://www.apache.org/licenses/LICENSE-2.0
13  *
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===================================
20  */
21 package org.oransc.portal.nonrtric.controlpanel.portalapi;
22
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;
29
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;
39
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;
55
56 /**
57  * This filter checks every request for the cookie set by the ONAP Portal single
58  * sign on process. The possible paths and actions:
59  * <OL>
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
66  * session.
67  * </OL>
68  * <P>
69  * Notes:
70  * <UL>
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.
74  * </UL>
75  *
76  * Open question: What about sessions? Will this be stateless?
77  *
78  * This filter uses no annotations to avoid Spring's automatic registration,
79  * which add this filter in the chain in the wrong order.
80  */
81 public class PortalAuthenticationFilter implements Filter {
82
83     private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
84
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"};
89
90     public static final String REDIRECT_URL_KEY = "redirectUrl";
91
92     private final boolean enforcePortalSecurity;
93     private final PortalAuthManager authManager;
94
95     private final ControlpanelUserManager userManager;
96
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);
106                 if (in == null) {
107                     String msg = "Failed to find property file on classpath: " + pf;
108                     logger.error(msg);
109                     throw new IOException(msg);
110                 } else {
111                     try {
112                         in.close();
113                     } catch (IOException ex) {
114                         logger.warn("Failed to close stream", ex);
115                     }
116                 }
117             }
118         }
119     }
120
121     @Override
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);
126         if (url == null)
127             logger
128                 .error("init: Failed to find property in portal.properties: " + PortalApiConstants.ECOMP_REDIRECT_URL);
129     }
130
131     @Override
132     public void destroy() {
133         // No resources to release
134     }
135
136     /**
137      * Requests for pages ignored in the web security config do not hit this filter.
138      */
139     @Override
140     public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
141         throws IOException, ServletException {
142         if (enforcePortalSecurity)
143             doFilterEPSDKFW(req, res, chain);
144         else
145             doFilterMockUserAdminRole(req, res, chain);
146     }
147
148     /*
149      * Populates security context with a mock user in the admin role.
150      *
151      */
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);
159             }
160             EcompRole admin = new EcompRole();
161             admin.setId(1L);
162             admin.setName(ControlpanelConstants.ROLE_ADMIN);
163             HashSet<EcompRole> roles = new HashSet<>();
164             roles.add(admin);
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);
173         } else {
174             logger.debug("doFilter: authorities {}", auth.getAuthorities());
175         }
176         chain.doFilter(req, res);
177     }
178
179     /*
180      * Checks for valid cookies and allows request to be served if found; redirects
181      * to Portal otherwise.
182      */
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();
200         } else {
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);
208         }
209     }
210
211     /**
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.
216      *
217      * @param appUrl
218      *        Original requested URL
219      * @return HTML
220      * @throws UnsupportedEncodingException
221      *         On error
222      */
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"), //
231             "<html>", //
232             "<head>", //
233             "<title>NonRT RIC Controlpanel</title>", //
234             "<style>", //
235             "html, body { ", //
236             "  font-family: Helvetica, Arial, sans-serif;", //
237             "}", //
238             "</style>", //
239             "</head>", //
240             "<body>", //
241             "<h2>NonRT RIC Controlpanel</h2>", //
242             "<h4>Please log in.</h4>", //
243             "<p>", //
244             aHref, "Click here to authenticate at the ONAP Portal</a>", //
245             "</p>", //
246             "</body>", //
247             "</html>");
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 }