Add configuration option for Portal security
[portal/ric-dashboard.git] / 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 and Nokia
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.PortalApiConstants;
40 import org.onap.portalsdk.core.onboarding.util.PortalApiProperties;
41 import org.onap.portalsdk.core.restful.domain.EcompRole;
42 import org.onap.portalsdk.core.restful.domain.EcompUser;
43 import org.oransc.ric.portal.dashboard.DashboardConstants;
44 import org.oransc.ric.portal.dashboard.model.EcompUserDetails;
45 import org.slf4j.Logger;
46 import org.slf4j.LoggerFactory;
47 import org.springframework.security.core.Authentication;
48 import org.springframework.security.core.context.SecurityContextHolder;
49 import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
50
51 /**
52  * This filter checks every request for the cookie set by the ONAP Portal single
53  * sign on process. The possible paths and actions:
54  * <OL>
55  * <LI>User starts at an app page via a bookmark. No Portal cookie is set.
56  * Redirect there to get one; then continue as below.
57  * <LI>User starts at Portal and goes to app. Alternately, the user's session
58  * times out and the user hits refresh. The Portal cookie is set, but there is
59  * no valid session. Create one and publish info.
60  * <LI>User has valid Portal cookie and session. Reset the max idle in that
61  * session.
62  * </OL>
63  * <P>
64  * Notes:
65  * <UL>
66  * <LI>While redirecting, the cookie "redirectUrl" should also be set so that
67  * Portal knows where to forward the request to once the Portal Session is
68  * created and EPService cookie is set.
69  * </UL>
70  * 
71  * TODO: What about sessions? Will this be stateless?
72  * 
73  * This filter uses no annotations to avoid Spring's automatic registration,
74  * which add this filter in the chain in the wrong order.
75  */
76 public class PortalAuthenticationFilter implements Filter {
77
78         private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
79
80         // Unfortunately these names are not available as constants
81         private static final String[] securityPropertyFiles = { "ESAPI.properties", "key.properties", "portal.properties",
82                         "validation.properties" };
83
84         public static final String REDIRECT_URL_KEY = "redirectUrl";
85
86         private final boolean enforcePortalSecurity;
87         private final PortalAuthManager authManager;
88
89         private final DashboardUserManager userManager;
90
91         public PortalAuthenticationFilter(boolean portalSecurity, PortalAuthManager authManager,
92                         DashboardUserManager userManager) {
93                 this.enforcePortalSecurity = portalSecurity;
94                 this.authManager = authManager;
95                 this.userManager = userManager;
96                 if (portalSecurity) {
97                         // Throw if security is requested and prerequisites are not met
98                         for (String pf : securityPropertyFiles) {
99                                 InputStream in = MethodHandles.lookup().lookupClass().getClassLoader().getResourceAsStream(pf);
100                                 if (in == null) {
101                                         String msg = "Failed to find property file on classpath: " + pf;
102                                         logger.error(msg);
103                                         throw new RuntimeException(msg);
104                                 } else {
105                                         try {
106                                                 in.close();
107                                         } catch (IOException ex) {
108                                                 logger.warn("Failed to close stream", ex);
109                                         }
110                                 }
111                         }
112                 }
113         }
114
115         @Override
116         public void init(FilterConfig filterConfig) throws ServletException {
117                 // complain loudly if this key property is missing
118                 String url = PortalApiProperties.getProperty(PortalApiConstants.ECOMP_REDIRECT_URL);
119                 logger.debug("init: Portal redirect URL {}", url);
120                 if (url == null)
121                         logger.error(
122                                         "init: Failed to find property in portal.properties: " + PortalApiConstants.ECOMP_REDIRECT_URL);
123         }
124
125         @Override
126         public void destroy() {
127                 // No resources to release
128         }
129
130         /**
131          * Requests for pages ignored in the web security config do not hit this filter.
132          */
133         @Override
134         public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
135                         throws IOException, ServletException {
136                 if (enforcePortalSecurity)
137                         doFilterEPSDKFW(req, res, chain);
138                 else
139                         doFilterMockUserAdminRole(req, res, chain);
140         }
141
142         /*
143          * Populates security context with a mock user in the admin role.
144          * 
145          */
146         private void doFilterMockUserAdminRole(ServletRequest req, ServletResponse res, FilterChain chain)
147                         throws IOException, ServletException {
148                 Authentication auth = SecurityContextHolder.getContext().getAuthentication();
149                 if (auth == null || auth.getAuthorities().isEmpty()) {
150                         logger.debug("doFilter adding auth to request {}", req);
151                         EcompRole admin = new EcompRole();
152                         admin.setId(1L);
153                         admin.setName(DashboardConstants.ROLE_ADMIN);
154                         HashSet<EcompRole> roles = new HashSet<>();
155                         roles.add(admin);
156                         EcompUser user = new EcompUser();
157                         user.setLoginId("fakeLoginId");
158                         user.setRoles(roles);
159                         user.setActive(true);
160                         EcompUserDetails userDetails = new EcompUserDetails(user);
161                         PreAuthenticatedAuthenticationToken authToken = new PreAuthenticatedAuthenticationToken(userDetails,
162                                         "fakeCredentials", userDetails.getAuthorities());
163                         SecurityContextHolder.getContext().setAuthentication(authToken);
164                 } else {
165                         logger.debug("doFilter: authorities {}", auth.getAuthorities());
166                 }
167                 chain.doFilter(req, res);
168         }
169
170         /*
171          * Checks for valid cookies and allows request to be served if found; redirects
172          * to Portal otherwise.
173          */
174         private void doFilterEPSDKFW(ServletRequest req, ServletResponse res, FilterChain chain)
175                         throws IOException, ServletException {
176                 logger.debug("doFilter {}", req);
177                 HttpServletRequest request = (HttpServletRequest) req;
178                 HttpServletResponse response = (HttpServletResponse) res;
179                 // Need to authenticate the request
180                 final String userId = authManager.valdiateEcompSso(request);
181                 final EcompUser ecompUser = (userId == null ? null : userManager.getUser(userId));
182                 if (userId == null || ecompUser == null) {
183                         String redirectURL = buildLoginPageUrl(request);
184                         logger.trace("doFilter: unauthorized, redirecting to {}", redirectURL);
185                         response.sendRedirect(redirectURL);
186                 } else {
187                         EcompUserDetails userDetails = new EcompUserDetails(ecompUser);
188                         // Using portal session as credentials is a hack
189                         PreAuthenticatedAuthenticationToken authToken = new PreAuthenticatedAuthenticationToken(userDetails,
190                                         getPortalSessionId(request), userDetails.getAuthorities());
191                         SecurityContextHolder.getContext().setAuthentication(authToken);
192                         // Pass request back down the filter chain
193                         chain.doFilter(request, response);
194                 }
195         }
196
197         private String buildLoginPageUrl(HttpServletRequest request) {
198                 logger.trace("buildLoginPageUrl");
199                 // Why so much work to recover the original request?
200                 final StringBuffer sb = request.getRequestURL();
201                 sb.append(request.getQueryString() == null ? "" : "?" + request.getQueryString());
202                 final String requestedUrl = sb.toString();
203                 String encodedUrl = null;
204                 try {
205                         encodedUrl = URLEncoder.encode(requestedUrl, "UTF-8");
206                 } catch (UnsupportedEncodingException ex) {
207                         logger.error("buildLoginPageUrl: Failed to encode {}", requestedUrl);
208                 }
209                 return DashboardConstants.LOGIN_PAGE + "?" + REDIRECT_URL_KEY + "=" + encodedUrl;
210         }
211
212         /**
213          * Searches the request for a cookie with the specified name.
214          *
215          * @param request
216          *                       HttpServletRequest
217          * @param cookieName
218          *                       Cookie name
219          * @return Cookie, or null if not found.
220          */
221         private Cookie getCookie(HttpServletRequest request, String cookieName) {
222                 Cookie[] cookies = request.getCookies();
223                 if (cookies != null)
224                         for (Cookie cookie : cookies)
225                                 if (cookie.getName().equals(cookieName))
226                                         return cookie;
227                 return null;
228         }
229
230         /**
231          * Gets the ECOMP Portal service cookie value.
232          * 
233          * @param request
234          * @return Cookie value, or null if not found.
235          */
236         private String getPortalSessionId(HttpServletRequest request) {
237                 Cookie ep = getCookie(request, PortalApiConstants.EP_SERVICE);
238                 if (ep == null)
239                         return null;
240                 return ep.getValue();
241         }
242
243 }