1   package com.atlassian.seraph.filter;
2   
3   import com.atlassian.seraph.SecurityService;
4   import com.atlassian.seraph.auth.AuthType;
5   import com.atlassian.seraph.auth.AuthenticationContext;
6   import com.atlassian.seraph.auth.Authenticator;
7   import com.atlassian.seraph.auth.PrincipalNormaliser;
8   import com.atlassian.seraph.config.SecurityConfig;
9   import com.atlassian.seraph.config.SecurityConfigFactory;
10  import com.atlassian.seraph.util.RedirectUtils;
11  import org.apache.log4j.Category;
12  
13  import javax.servlet.*;
14  import javax.servlet.http.Cookie;
15  import javax.servlet.http.HttpServletRequest;
16  import javax.servlet.http.HttpServletResponse;
17  import java.io.IOException;
18  import java.security.Principal;
19  import java.util.HashSet;
20  import java.util.Set;
21  
22  /**
23   * The SecurityFilter determines which roles are required for a given request by querying all of the Services.
24   *
25   * @see SecurityService
26   */
27  public class SecurityFilter implements Filter
28  {
29      private FilterConfig config = null;
30      private SecurityConfig securityConfig = null;
31  
32      private static final Category log = Category.getInstance(SecurityFilter.class);
33      static final String ALREADY_FILTERED = "os_securityfilter_already_filtered";
34      public static final String ORIGINAL_URL = "atlassian.core.seraph.original.url";
35  
36      public void init(final FilterConfig config)
37      {
38          log.debug("SecurityFilter.init");
39          this.config = config;
40  
41          String configFileLocation = null;
42  
43          if (config.getInitParameter("config.file") != null)
44          {
45              configFileLocation = config.getInitParameter("config.file");
46              log.debug("Security config file location: " + configFileLocation);
47          }
48  
49          securityConfig = SecurityConfigFactory.getInstance(configFileLocation);
50          config.getServletContext().setAttribute(SecurityConfig.STORAGE_KEY, securityConfig);
51          log.debug("SecurityFilter.init completed successfully.");
52      }
53  
54      public void destroy()
55      {
56          log.debug("SecurityFilter.destroy");
57          // SER-129: securityConfig was seen in the wild to sometimes throw NPE.
58          if (securityConfig == null)
59          {
60              log.warn("Trying to destroy a SecurityFilter with null securityConfig.");
61          }
62          else
63          {
64              // TODO: Why do we set these variables to null? Note that the securityConfig still lives in the ServletContext attribute.
65              securityConfig.destroy();
66              securityConfig = null;
67          }
68          config = null;
69      }
70  
71      public void doFilter(final ServletRequest req, final ServletResponse res, final FilterChain chain) throws IOException, ServletException
72      {
73          if ((req.getAttribute(ALREADY_FILTERED) != null) || !getSecurityConfig().getController().isSecurityEnabled())
74          {
75              chain.doFilter(req, res);
76              return;
77          }
78          else
79          {
80              req.setAttribute(ALREADY_FILTERED, Boolean.TRUE);
81          }
82  
83          final String METHOD = "doFilter : ";
84          final boolean dbg = log.isDebugEnabled();
85  
86          // Try and get around Orion's bug when redeploying
87          // it seems that filters are not called in the correct order
88          if (req.getAttribute(BaseLoginFilter.ALREADY_FILTERED) == null)
89          {
90              log.warn(METHOD + "LoginFilter not yet applied to this request - terminating filter chain");
91              return;
92          }
93  
94          final HttpServletRequest httpServletRequest = (HttpServletRequest) req;
95          final HttpServletResponse httpServletResponse = (HttpServletResponse) res;
96  
97          final String originalURL = httpServletRequest.getServletPath() + (httpServletRequest.getPathInfo() == null ? "" : httpServletRequest.getPathInfo()) + (httpServletRequest.getQueryString() == null ? "" : "?" + httpServletRequest.getQueryString());
98  
99          // store the original URL as a request attribute anyway - often useful for pages to access it (ie login links)
100         httpServletRequest.setAttribute(SecurityFilter.ORIGINAL_URL, originalURL);
101         if (dbg)
102         {
103             log.debug(METHOD + "Storing the originally requested URL (" + SecurityFilter.ORIGINAL_URL + "=" + originalURL + ")");
104         }
105 
106         final Set<String> requiredRoles = new HashSet<String>();
107 
108         // loop through loaded services and get required roles
109         for (final SecurityService service : getSecurityConfig().getServices())
110         {
111             final Set<String> serviceRoles = service.getRequiredRoles(httpServletRequest);
112             requiredRoles.addAll(serviceRoles);
113         }
114 
115         if (dbg)
116         {
117             log.debug(METHOD + "requiredRoles = " + requiredRoles);
118         }
119 
120         // whether this URL needs authorisation
121         boolean needAuth = false;
122 
123         // try to get the user (for cookie logins and basic auth as well as already established sessions)
124         final Authenticator authenticator = getSecurityConfig().getAuthenticator();
125         Principal user = authenticator.getUser(httpServletRequest, httpServletResponse);
126         if (authenticator instanceof PrincipalNormaliser)
127         {
128             user = ((PrincipalNormaliser) authenticator).normalisePrincipal(httpServletRequest,user);
129         }
130 
131         if (user == null)
132         {
133             AuthType authType = AuthType.getAuthTypeInformation(httpServletRequest, getSecurityConfig());
134 
135             // This include AuthType.BASIC *as well as* when the client pre-emptively sends BASIC credentials
136             // This will also catch AuthType.ANY when they send BASIC credentials.
137             if (RedirectUtils.isBasicAuthentication(httpServletRequest, getSecurityConfig().getAuthType()))
138             {
139                 // The DefaultAuthenticator already did a response.sendError(401)
140                 return;
141             }
142             else if (authType == AuthType.COOKIE && httpServletRequest.getSession(false) == null)
143             {
144                 httpServletResponse.sendError(401, "os_authType was 'cookie' but no valid cookie was sent.");
145                 return;
146             }
147             else if (authType == AuthType.ANY)
148             {
149                 // If they specified AuthType.ANY and sent BASIC credentials it was handled by a previous case.
150                 // For this we only have to worry about cookies.
151                 if (hasJSessionCookie(httpServletRequest.getCookies()) && httpServletRequest.getSession(false) == null)
152                 {
153                     httpServletResponse.sendError(401, "os_authType was 'any' and an invalid cookie was sent.");
154                     return;
155                 }
156             }
157         }
158 
159         // set the user in the context
160         if (dbg)
161         {
162             log.debug(METHOD + "Setting Auth Context to be '" + (user == null ? "anonymous " : user.getName()) + "'");
163         }
164 
165         final AuthenticationContext authenticationContext = getAuthenticationContext();
166         authenticationContext.setUser(user);
167 
168         // check if the current user has all required permissions
169         // if there is no current user, request.isUserInRole() always returns false so this works
170         for (final Object element : requiredRoles)
171         {
172             final String role = (String) element;
173 
174             // this isUserInRole method is only used here and 'd be better off replaced by getRoleMapper().hasRole(user, request, role)) since we have the user already
175             // was : if (!securityConfig.getAuthenticator().isUserInRole(request, role))
176             if (!getSecurityConfig().getRoleMapper().hasRole(user, httpServletRequest, role))
177             {
178                 log.info(METHOD + "'" + user + "' needs (and lacks) role '" + role + "' to access " + originalURL);
179                 needAuth = true;
180             }
181         }
182 
183         // check if we're at the signon page, in which case do not auth
184         if ((httpServletRequest.getServletPath() != null) && httpServletRequest.getServletPath().equals(getSecurityConfig().getLoginURL()))
185         {
186             if (dbg)
187             {
188                 log.debug(METHOD + "Login page requested so no additional authorization required.");
189             }
190             needAuth = false;
191         }
192 
193         // if we need to authenticate, store current URL and forward
194         if (needAuth)
195         {
196             if (dbg)
197             {
198                 log.debug(METHOD + "Need Authentication: Redirecting to: " + getSecurityConfig().getLoginURL() + " from: " + originalURL);
199             }
200 
201             httpServletRequest.getSession().setAttribute(getSecurityConfig().getOriginalURLKey(), originalURL);
202             // only redirect if we can. if isCommited==true, there might have been a redirection requested by a LoginInterceptor, for instance.
203             if (!httpServletResponse.isCommitted())
204             {
205                 httpServletResponse.sendRedirect(RedirectUtils.getLoginUrl(httpServletRequest));
206             }
207             // Suppress the IDEA compile warning. This return is unnecessary, but good for safety against future code changes.
208             //noinspection UnnecessaryReturnStatement
209             return;
210         }
211         else
212         {
213             try
214             {
215                 chain.doFilter(req, res);
216             }
217             finally
218             {
219                 // clear the user from the context
220                 authenticationContext.clearUser();
221             }
222         }
223     }
224 
225     private boolean hasJSessionCookie(final Cookie[] cookies)
226     {
227         if (cookies == null)
228         {
229             return false;
230         }
231 
232         for (Cookie cookie : cookies)
233         {
234             if (cookie.getName().equals("JSESSIONID"))
235             {
236                 return true;
237             }
238         }
239 
240         return false;
241     }
242 
243     protected SecurityConfig getSecurityConfig()
244     {
245         // ?? is this any useful, since we already initalize this in the init method ??
246         if (securityConfig == null)
247         {
248             securityConfig = (SecurityConfig) config.getServletContext().getAttribute(SecurityConfig.STORAGE_KEY);
249         }
250         return securityConfig;
251     }
252 
253     protected AuthenticationContext getAuthenticationContext()
254     {
255         return getSecurityConfig().getAuthenticationContext();
256     }
257 
258 }