1   package com.atlassian.security.auth.trustedapps.filter;
2   
3   import java.security.Principal;
4   import java.security.PublicKey;
5   
6   import javax.servlet.http.HttpServletRequest;
7   import javax.servlet.http.HttpServletResponse;
8   
9   import com.atlassian.security.auth.trustedapps.ApplicationCertificate;
10  import com.atlassian.security.auth.trustedapps.DefaultEncryptedCertificate;
11  import com.atlassian.security.auth.trustedapps.InvalidCertificateException;
12  import com.atlassian.security.auth.trustedapps.filter.RequestSignatureTool.UnableToVerifySignatureException;
13  import com.atlassian.security.auth.trustedapps.TransportErrorMessage;
14  import com.atlassian.security.auth.trustedapps.TrustedApplication;
15  import com.atlassian.security.auth.trustedapps.TrustedApplicationUtils;
16  import com.atlassian.security.auth.trustedapps.TrustedApplicationsManager;
17  import com.atlassian.security.auth.trustedapps.UserResolver;
18  
19  import org.slf4j.Logger;
20  import org.slf4j.LoggerFactory;
21  
22  public class TrustedApplicationFilterAuthenticator implements Authenticator
23  {
24      private static final Logger log = LoggerFactory.getLogger(TrustedApplicationFilterAuthenticator.class);
25  
26      final TrustedApplicationsManager appManager;
27      final UserResolver resolver;
28      final AuthenticationController authenticationController;
29  
30      public TrustedApplicationFilterAuthenticator(TrustedApplicationsManager appManager, UserResolver resolver, AuthenticationController authenticationController)
31      {
32          this.appManager = appManager;
33          this.resolver = resolver;
34          this.authenticationController = authenticationController;
35      }
36      
37      private static boolean atLeast(Integer protocolVersion, int required)
38      {
39          return protocolVersion != null && protocolVersion.intValue() >= required;
40      }
41      
42      public Result authenticate(HttpServletRequest request, HttpServletResponse response)
43      {
44          final String certStr = request.getHeader(TrustedApplicationUtils.Header.Request.CERTIFICATE);
45          if (isBlank(certStr))
46          {
47              return new Result.NoAttempt();
48          }
49  
50          final String id = request.getHeader(TrustedApplicationUtils.Header.Request.ID);
51          if (isBlank(id))
52          {
53              final Result.Error result = new Result.Error(new TransportErrorMessage.ApplicationIdNotFoundInRequest());
54              setFailureHeader(response, result.getMessage());
55              return result;
56          }
57  
58          final String key = request.getHeader(TrustedApplicationUtils.Header.Request.SECRET_KEY);
59          if (isBlank(key))
60          {
61              final Result.Error result = new Result.Error(new TransportErrorMessage.SecretKeyNotFoundInRequest());
62              setFailureHeader(response, result.getMessage());
63              return result;
64          }
65  
66          final String magicNumber = request.getHeader(TrustedApplicationUtils.Header.Request.MAGIC);
67          // magic number validation is only done from protocol version 2, version 1 had no version header
68          final String version = request.getHeader(TrustedApplicationUtils.Header.Request.VERSION);
69          final Integer protocolVersion;
70          try
71          {
72              protocolVersion = (!isBlank(version)) ? Integer.parseInt(version) : null;
73          }
74          catch (NumberFormatException e)
75          {
76              final Result.Error result = new Result.Error(new TransportErrorMessage.BadProtocolVersion(version));
77              setFailureHeader(response, result.getMessage());
78              return result;
79          }
80  
81          if (atLeast(protocolVersion, 1))
82          {
83              if (isBlank(magicNumber))
84              {
85                  final Result.Error result = new Result.Error(new TransportErrorMessage.MagicNumberNotFoundInRequest());
86                  setFailureHeader(response, result.getMessage());
87                  return result;
88              }
89          }
90  
91          TrustedApplication app = appManager.getTrustedApplication(id);
92          if (app == null)
93          {
94              final Result.Failure result = new Result.Failure(new TransportErrorMessage.ApplicationUnknown(id));
95              setFailureHeader(response, result.getMessage());
96              return result;
97          }
98  
99          final ApplicationCertificate certificate;
100         try
101         {
102             certificate = app.decode(new DefaultEncryptedCertificate(id, key, certStr, protocolVersion, magicNumber), request);
103         }
104         catch (InvalidCertificateException ex)
105         {
106             log.warn("Failed to login trusted application: " + app.getID() + " due to: " + ex);
107             // debug for stacktrace, no need for isDebugEnabled check as there is no string concatenation
108             log.debug("Failed to login trusted application cause", ex);
109             final Result.Error result = new Result.Error(ex.getTransportErrorMessage());
110             setFailureHeader(response, result.getMessage());
111             return result;
112         }
113         
114         String signedRequestUrl;
115         
116         final String signature = request.getHeader(TrustedApplicationUtils.Header.Request.SIGNATURE);
117         
118         if (atLeast(protocolVersion, 2) && signature == null)
119         {
120             final Result.Error result = new Result.Error(new TransportErrorMessage.BadSignature());
121             setFailureHeader(response, result.getMessage());
122             return result;
123         }
124         
125         if (signature != null)
126         {
127             String expectedSignedUrl;
128             
129             StringBuffer sb = request.getRequestURL();
130             String q = request.getQueryString();
131             if (q != null)
132             {
133                 sb.append('?');
134                 sb.append(q);
135             }
136             
137             expectedSignedUrl = sb.toString();
138             
139             try
140             {
141                 PublicKey publicKey = app.getPublicKey();
142                 if (new RequestSignatureTool().verify(certificate.getCreationTime().getTime(), expectedSignedUrl, publicKey, signature))
143                 {
144                     signedRequestUrl = expectedSignedUrl;
145                 }
146                 else
147                 {
148                     log.warn("Failed to login trusted application: " + app.getID() + " due to bad URL signature.");
149                     
150                     final Result.Error result = new Result.Error(new TransportErrorMessage.BadSignature(expectedSignedUrl));
151                     setFailureHeader(response, result.getMessage());
152                     return result;
153                 }
154             }
155             catch (UnableToVerifySignatureException e)
156             {
157                 log.warn("Failed to login trusted application: " + app.getID() + " due to: " + e);
158                 
159                 final Result.Error result = new Result.Error(new TransportErrorMessage.BadSignature(expectedSignedUrl));
160                 setFailureHeader(response, result.getMessage());
161                 return result;
162             }
163         }
164         else
165         {
166             signedRequestUrl = null;
167         }
168         
169         // Allow a request past this point without a signed URL
170         
171         final Principal user = resolver.resolve(certificate);
172         if (user == null)
173         {
174             log.warn("User '" + certificate.getUserName() + "' referenced by trusted application: '" + app.getID() + "' is not found.");
175             final Result.Failure result = new Result.Failure(new TransportErrorMessage.UserUnknown(certificate.getUserName()));
176             setFailureHeader(response, result.getMessage());
177             return result;
178         }
179         else if (!authenticationController.canLogin(user, request))
180         {
181             // user exists but is not allowed to login
182             log.warn("User '" + certificate.getUserName() + "' referenced by trusted application: '" + app.getID() + "' cannot login.");
183             final Result.Failure result = new Result.Failure(new TransportErrorMessage.PermissionDenied());
184             setFailureHeader(response, result.getMessage());
185             return result;
186         }
187 
188         if (signedRequestUrl != null)
189         {
190             return new Result.Success(user, signedRequestUrl);
191         }
192         else
193         {
194             return new Result.Success(user);
195         }
196     }
197 
198     private static void setFailureHeader(HttpServletResponse response, String message)
199     {
200         response.setHeader(TrustedApplicationUtils.Header.Response.STATUS, TrustedApplicationsFilter.Status.ERROR);
201         response.addHeader(TrustedApplicationUtils.Header.Response.ERROR, message);
202         if (log.isDebugEnabled())
203         {
204             log.debug(message, new RuntimeException(message));
205         }
206     }
207 
208     private static boolean isBlank(String input)
209     {
210         return (input == null) || input.trim().length() == 0;
211     }
212 }