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.config.SecurityConfig;
8   import com.atlassian.seraph.config.SecurityConfigFactory;
9   import com.atlassian.seraph.util.RedirectUtils;
10  import com.atlassian.seraph.util.SecurityUtils;
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 (!SecurityUtils.isSeraphFilteringDisabled(req))
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         final Principal user = authenticator.getUser(httpServletRequest, httpServletResponse);
126 
127         if (user == null)
128         {
129             AuthType authType = AuthType.getAuthTypeInformation(httpServletRequest, getSecurityConfig());
130 
131             // This include AuthType.BASIC *as well as* when the client pre-emptively sends BASIC credentials
132             // This will also catch AuthType.ANY when they send BASIC credentials.
133             if (RedirectUtils.isBasicAuthentication(httpServletRequest, getSecurityConfig().getAuthType()))
134             {
135                 // The DefaultAuthenticator already did a response.sendError(401)
136                 return;
137             }
138             else if (authType == AuthType.COOKIE && httpServletRequest.getSession(false) == null)
139             {
140                 httpServletResponse.sendError(401, "os_authType was 'cookie' but no valid cookie was sent.");
141                 return;
142             }
143             else if (authType == AuthType.ANY)
144             {
145                 // If they specified AuthType.ANY and sent BASIC credentials it was handled by a previous case.
146                 // For this we only have to worry about cookies.
147                 if (hasJSessionCookie(httpServletRequest.getCookies()) && httpServletRequest.getSession(false) == null)
148                 {
149                     httpServletResponse.sendError(401, "os_authType was 'any' and an invalid cookie was sent.");
150                     return;
151                 }
152             }
153         }
154 
155         // set the user in the context
156         if (dbg)
157         {
158             log.debug(METHOD + "Setting Auth Context to be '" + (user == null ? "anonymous " : user.getName()) + "'");
159         }
160 
161         final AuthenticationContext authenticationContext = getAuthenticationContext();
162         authenticationContext.setUser(user);
163 
164         // check if the current user has all required permissions
165         // if there is no current user, request.isUserInRole() always returns false so this works
166         for (final Object element : requiredRoles)
167         {
168             final String role = (String) element;
169 
170             // 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
171             // was : if (!securityConfig.getAuthenticator().isUserInRole(request, role))
172             if (!getSecurityConfig().getRoleMapper().hasRole(user, httpServletRequest, role))
173             {
174                 log.info(METHOD + "'" + user + "' needs (and lacks) role '" + role + "' to access " + originalURL);
175                 needAuth = true;
176             }
177         }
178 
179         // check if we're at the signon page, in which case do not auth
180         if ((httpServletRequest.getServletPath() != null) && httpServletRequest.getServletPath().equals(getSecurityConfig().getLoginURL()))
181         {
182             if (dbg)
183             {
184                 log.debug(METHOD + "Login page requested so no additional authorization required.");
185             }
186             needAuth = false;
187         }
188 
189         // if we need to authenticate, store current URL and forward
190         if (needAuth)
191         {
192             if (dbg)
193             {
194                 log.debug(METHOD + "Need Authentication: Redirecting to: " + getSecurityConfig().getLoginURL() + " from: " + originalURL);
195             }
196 
197             httpServletRequest.getSession().setAttribute(getSecurityConfig().getOriginalURLKey(), originalURL);
198             // only redirect if we can. if isCommited==true, there might have been a redirection requested by a LoginInterceptor, for instance.
199             if (!httpServletResponse.isCommitted())
200             {
201                 httpServletResponse.sendRedirect(RedirectUtils.getLoginUrl(httpServletRequest));
202             }
203             // Suppress the IDEA compile warning. This return is unnecessary, but good for safety against future code changes.
204             //noinspection UnnecessaryReturnStatement
205             return;
206         }
207         else
208         {
209             try
210             {
211                 chain.doFilter(req, res);
212             }
213             finally
214             {
215                 // clear the user from the context
216                 authenticationContext.clearUser();
217             }
218         }
219     }
220 
221     private boolean hasJSessionCookie(final Cookie[] cookies)
222     {
223         if (cookies == null)
224         {
225             return false;
226         }
227 
228         for (Cookie cookie : cookies)
229         {
230             if (cookie.getName().equals("JSESSIONID"))
231             {
232                 return true;
233             }
234         }
235 
236         return false;
237     }
238 
239     protected SecurityConfig getSecurityConfig()
240     {
241         // ?? is this any useful, since we already initalize this in the init method ??
242         if (securityConfig == null)
243         {
244             securityConfig = (SecurityConfig) config.getServletContext().getAttribute(SecurityConfig.STORAGE_KEY);
245         }
246         return securityConfig;
247     }
248 
249     protected AuthenticationContext getAuthenticationContext()
250     {
251         return getSecurityConfig().getAuthenticationContext();
252     }
253 
254 }