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.CurrentApplication;
11  import com.atlassian.security.auth.trustedapps.DefaultApplicationCertificate;
12  import com.atlassian.security.auth.trustedapps.EncryptedCertificate;
13  import com.atlassian.security.auth.trustedapps.InvalidCertificateException;
14  import com.atlassian.security.auth.trustedapps.InvalidRemoteAddressException;
15  import com.atlassian.security.auth.trustedapps.InvalidRequestUrlException;
16  import com.atlassian.security.auth.trustedapps.InvalidXForwardedForAddressException;
17  import com.atlassian.security.auth.trustedapps.RequestConditions;
18  import com.atlassian.security.auth.trustedapps.SystemException;
19  import com.atlassian.security.auth.trustedapps.TrustedApplication;
20  import com.atlassian.security.auth.trustedapps.TrustedApplicationUtils;
21  import com.atlassian.security.auth.trustedapps.TrustedApplicationsManager;
22  import com.atlassian.security.auth.trustedapps.UserResolver;
23  
24  import junit.framework.TestCase;
25  
26  import static org.mockito.Mockito.mock;
27  import static org.mockito.Mockito.when;
28  
29  public class TestTrustedAppAuthenticatorImpl extends TestCase
30  {
31      private static final String USER = "blad-de-blah-blah";
32      private final PublicKey publicKey = new PublicKey()
33      {
34          public String getAlgorithm()
35          {
36              return "algy";
37          }
38  
39          public byte[] getEncoded()
40          {
41              return new byte[] { 4, 3, 2, 1 };
42          }
43  
44          public String getFormat()
45          {
46              return "format";
47          }
48      };
49  
50      private final TrustedApplicationsManager manager = new TrustedApplicationsManager()
51      {
52          final CurrentApplication me = new CurrentApplication()
53          {
54              public String getID()
55              {
56                  return "me-id";
57              }
58  
59              public PublicKey getPublicKey()
60              {
61                  return publicKey;
62              }
63  
64              public EncryptedCertificate encode(final String userName)
65              {
66                  EncryptedCertificate cert = mock(EncryptedCertificate.class);
67                  when(cert.getCertificate()).thenReturn(userName);
68                  when(cert.getID()).thenReturn("me-id");
69                  when(cert.getMagicNumber()).thenReturn("1");
70                  when(cert.getProtocolVersion()).thenReturn(Integer.valueOf(1));
71                  when(cert.getSecretKey()).thenReturn("secret");
72                  return cert;
73              }
74              
75              public EncryptedCertificate encode(String userName, String urlToSign)
76              {
77                  throw new UnsupportedOperationException();
78              }
79          };
80  
81          public CurrentApplication getCurrentApplication()
82          {
83              return me;
84          }
85  
86          public TrustedApplication getTrustedApplication(String id)
87          {
88              if (id.equals("me-id"))
89              {
90                  return new TrustedApplication()
91                  {
92                      public ApplicationCertificate decode(EncryptedCertificate certificate, HttpServletRequest request) throws InvalidCertificateException
93                      {
94                          if (request.getHeader("ip-mismatch") != null)
95                          {
96                              throw new InvalidCertificateException(new InvalidRemoteAddressException(request.getRemoteAddr()));
97                          }
98                          if (request.getHeader("forward-mismatch") != null)
99                          {
100                             throw new InvalidCertificateException(new InvalidXForwardedForAddressException(request.getRemoteAddr()));
101                         }
102                         if (request.getHeader("url-mismatch") != null)
103                         {
104                             throw new InvalidCertificateException(new InvalidRequestUrlException(request.getPathInfo()));
105                         }
106                         if ("bad-cert".equals(certificate.getCertificate()))
107                         {
108                             throw new InvalidCertificateException(new SystemException("bad-cert", new NullPointerException("what a bad certificate!")));
109                         }
110                         return new DefaultApplicationCertificate("me-id", certificate.getCertificate(), System.currentTimeMillis());
111                     }
112 
113                     public PublicKey getPublicKey()
114                     {
115                         return publicKey;
116                     }
117 
118                     public String getID()
119                     {
120                         return "me-id";
121                     }
122 
123                     public RequestConditions getRequestConditions()
124                     {
125                         return null;
126                     }
127 
128                     public String getName()
129                     {
130                         return null;
131                     }
132                 };
133             }
134             return null;
135         }
136     };
137 
138     private final Principal principal = new Principal()
139     {
140         public String getName()
141         {
142             return USER;
143         }
144     };
145 
146     private final UserResolver resolver = new UserResolver()
147     {
148         public Principal resolve(ApplicationCertificate certificate)
149         {
150             return (certificate.getUserName().equals(principal.getName())) ? principal : null;
151         };
152     };
153 
154     private final boolean[] canLogin = new boolean[] { true };
155     private final AuthenticationController authenticationController = new AuthenticationController()
156     {
157         public boolean shouldAttemptAuthentication(HttpServletRequest request)
158         {
159             return true;
160         }
161 
162         public boolean canLogin(Principal user, HttpServletRequest request)
163         {
164             return canLogin[0];
165         }
166     };
167 
168     private final Authenticator authenticator = new TrustedApplicationFilterAuthenticator(manager, resolver, authenticationController)
169     {
170         public Result authenticate(HttpServletRequest request, HttpServletResponse response)
171         {
172             return super.authenticate(ImmutableRequest.wrap(request), response);
173         }
174     };
175 
176     protected void setUp() throws Exception
177     {
178         canLogin[0] = true;
179     }
180 
181     // -----------------------------------------------------------------------------------------------------------------
182     // tests
183     // -----------------------------------------------------------------------------------------------------------------
184 
185     public void testKnownAppProtocolVersion0() throws Exception
186     {
187         CurrentApplication me = manager.getCurrentApplication();
188         EncryptedCertificate cert = me.encode(USER);
189         MockRequest request = new MockTrustedAppRequestV0(cert);
190 
191         Authenticator.Result result = authenticator.authenticate(request, new MockResponse());
192 
193         assertNotNull(result);
194         assertEquals(Authenticator.Result.Status.SUCCESS, result.getStatus());
195     }
196 
197     public void testKnownAppProtocolVersion1() throws Exception
198     {
199         CurrentApplication me = manager.getCurrentApplication();
200         EncryptedCertificate cert = me.encode(USER);
201         MockTrustedAppRequestV1 request = new MockTrustedAppRequestV1(cert);
202 
203         Authenticator.Result result = authenticator.authenticate(request, new MockResponse());
204 
205         assertNotNull(result);
206         assertEquals(Authenticator.Result.Status.SUCCESS, result.getStatus());
207         assertNotNull(result.getUser());
208     }
209 
210     public void testUnknownApp() throws Exception
211     {
212         EncryptedCertificate cert = mock(EncryptedCertificate.class);
213         when(cert.getCertificate()).thenReturn("cert");
214         when(cert.getID()).thenReturn("appId");
215         when(cert.getMagicNumber()).thenReturn("majick");
216         when(cert.getSecretKey()).thenReturn("dis-is-a-sekrit");
217         
218         MockTrustedAppRequestV1 request = new MockTrustedAppRequestV1(cert);
219         // CurrentApplication me = manager.getCurrentApplication();
220 
221         Authenticator.Result result = authenticator.authenticate(request, new MockResponse());
222 
223         assertNotNull(result);
224         assertEquals(Authenticator.Result.Status.FAILED, result.getStatus());
225         assertEquals("APP_UNKNOWN;\tUnknown Application: {0};\t[\"appId\"]", result.getMessage());
226         assertNull(result.getUser());
227     }
228 
229     public void testBlankAppId() throws Exception
230     {
231         EncryptedCertificate cert = mock(EncryptedCertificate.class);
232         when(cert.getCertificate()).thenReturn("cert");
233         when(cert.getMagicNumber()).thenReturn("majick");
234         when(cert.getSecretKey()).thenReturn("dis-is-a-sekrit");
235         
236         MockTrustedAppRequestV1 request = new MockTrustedAppRequestV1(cert);
237         // CurrentApplication me = manager.getCurrentApplication();
238 
239         Authenticator.Result result = authenticator.authenticate(request, new MockResponse());
240 
241         assertNotNull(result);
242         assertEquals(Authenticator.Result.Status.ERROR, result.getStatus());
243         assertEquals("APP_ID_NOT_FOUND;\tApplication ID not found in request;\t[]", result.getMessage());
244         assertNull(result.getUser());
245     }
246 
247     public void testBadProtocolVersion() throws Exception
248     {
249         EncryptedCertificate cert = mock(EncryptedCertificate.class);
250         when(cert.getCertificate()).thenReturn("cert");
251         when(cert.getID()).thenReturn("appId");
252         when(cert.getMagicNumber()).thenReturn("majick");
253         when(cert.getSecretKey()).thenReturn("dis-is-a-sekrit");
254         
255         MockTrustedAppRequestV1 request = new MockTrustedAppRequestV1(cert);
256         request.addHeader(TrustedApplicationUtils.Header.Request.VERSION, "a-dodgy-version");
257 
258         Authenticator.Result result = authenticator.authenticate(request, new MockResponse());
259 
260         assertNotNull(result);
261         assertEquals(Authenticator.Result.Status.ERROR, result.getStatus());
262         assertEquals("BAD_PROTOCOL_VERSION;\tBad protocol version: {0};\t[\"a-dodgy-version\"]", result.getMessage());
263         assertNull(result.getUser());
264     }
265 
266     public void testMissingSecretKey() throws Exception
267     {
268         EncryptedCertificate cert = mock(EncryptedCertificate.class);
269         when(cert.getCertificate()).thenReturn("cert");
270         when(cert.getID()).thenReturn("appId");
271         when(cert.getMagicNumber()).thenReturn("majick");
272         when(cert.getSecretKey()).thenReturn("");
273         
274         MockTrustedAppRequestV1 request = new MockTrustedAppRequestV1(cert);
275 
276         Authenticator.Result result = authenticator.authenticate(request, new MockResponse());
277 
278         assertNotNull(result);
279         assertEquals(Authenticator.Result.Status.ERROR, result.getStatus());
280         assertEquals("SECRET_KEY_NOT_FOUND;\tSecret Key not found in request;\t[]", result.getMessage());
281         assertNull(result.getUser());
282     }
283 
284     public void testMissingMagicNumber() throws Exception
285     {
286         EncryptedCertificate cert = mock(EncryptedCertificate.class);
287         when(cert.getCertificate()).thenReturn("cert");
288         when(cert.getID()).thenReturn("appId");
289         when(cert.getMagicNumber()).thenReturn(null);
290         when(cert.getSecretKey()).thenReturn("dis-is-a-sekrit");
291         when(cert.getProtocolVersion()).thenReturn(Integer.valueOf(1));
292         
293         MockTrustedAppRequestV1 request = new MockTrustedAppRequestV1(cert);
294 
295         Authenticator.Result result = authenticator.authenticate(request, new MockResponse());
296 
297         assertNotNull(result);
298         assertEquals(Authenticator.Result.Status.ERROR, result.getStatus());
299         assertEquals("MAGIC_NUMBER_NOT_FOUND;\tMagic Number not found in request;\t[]", result.getMessage());
300         assertNull(result.getUser());
301     }
302 
303     public void testBadCertificate() throws Exception
304     {
305         CurrentApplication me = manager.getCurrentApplication();
306         EncryptedCertificate cert = me.encode(USER);
307         MockTrustedAppRequestV1 request = new MockTrustedAppRequestV1(cert);
308         request.addHeader(TrustedApplicationUtils.Header.Request.CERTIFICATE, "bad-cert");
309 
310         Authenticator.Result result = authenticator.authenticate(request, new MockResponse());
311 
312         assertNotNull(result);
313         assertEquals(result.getMessage(), Authenticator.Result.Status.ERROR, result.getStatus());
314         assertEquals("SYSTEM;\tException: {0} occurred serving request for application: {1};\t[\"java.lang.NullPointerException: what a bad certificate!\",\"bad-cert\"]", result.getMessage());
315         assertNull(result.getUser());
316     }
317 
318     public void testPrincipalNotFound() throws Exception
319     {
320         CurrentApplication me = manager.getCurrentApplication();
321         EncryptedCertificate cert = me.encode("unknown-principal");
322         MockTrustedAppRequestV1 request = new MockTrustedAppRequestV1(cert);
323 
324         Authenticator.Result result = authenticator.authenticate(request, new MockResponse());
325 
326         assertNotNull(result);
327         assertEquals(result.getMessage(), Authenticator.Result.Status.FAILED, result.getStatus());
328         assertEquals("USER_UNKNOWN;\tUnknown User: {0};\t[\"unknown-principal\"]", result.getMessage());
329         assertNull(result.getUser());
330     }
331 
332     public void testPrincipalLoginDenied() throws Exception
333     {
334         CurrentApplication me = manager.getCurrentApplication();
335         EncryptedCertificate cert = me.encode(USER);
336         MockTrustedAppRequestV1 request = new MockTrustedAppRequestV1(cert);
337 
338         canLogin[0] = false;
339         Authenticator.Result result = authenticator.authenticate(request, new MockResponse());
340 
341         assertNotNull(result);
342         assertEquals(result.getMessage(), Authenticator.Result.Status.FAILED, result.getStatus());
343         assertEquals("PERMISSION_DENIED;\tPermission Denied;\t[]", result.getMessage());
344         assertNull(result.getUser());
345     }
346 
347     public void testBadRequestIp() throws Exception
348     {
349         CurrentApplication me = manager.getCurrentApplication();
350         EncryptedCertificate cert = me.encode(USER);
351         MockTrustedAppRequestV1 request = new MockTrustedAppRequestV1(cert);
352         request.addHeader("ip-mismatch", "true");
353 
354         Authenticator.Result result = authenticator.authenticate(request, new MockResponse());
355 
356         assertNotNull(result);
357         assertEquals(result.getMessage(), Authenticator.Result.Status.ERROR, result.getStatus());
358         assertEquals("BAD_REMOTE_IP;\tRequest not allowed from IP address: {0};\t[\"i.am.a.teapot\"]", result.getMessage());
359         assertNull(result.getUser());
360     }
361 
362     public void testBadXForwardIp() throws Exception
363     {
364         CurrentApplication me = manager.getCurrentApplication();
365         EncryptedCertificate cert = me.encode(USER);
366         MockTrustedAppRequestV1 request = new MockTrustedAppRequestV1(cert);
367         request.addHeader("forward-mismatch", "true");
368 
369         Authenticator.Result result = authenticator.authenticate(request, new MockResponse());
370 
371         assertNotNull(result);
372         assertEquals(Authenticator.Result.Status.ERROR, result.getStatus());
373         assertEquals("BAD_XFORWARD_IP;\tRequest not allowed from IP address: {0};\t[\"i.am.a.teapot\"]", result.getMessage());
374         assertNull(result.getUser());
375     }
376 
377     public void testBadRequestUrl() throws Exception
378     {
379         CurrentApplication me = manager.getCurrentApplication();
380         EncryptedCertificate cert = me.encode(USER);
381         MockTrustedAppRequestV1 request = new MockTrustedAppRequestV1("/jira/secure/DeleteProject.jspa", cert);
382         request.addHeader("url-mismatch", "true");
383 
384         Authenticator.Result result = authenticator.authenticate(request, new MockResponse());
385 
386         assertNotNull(result);
387         assertEquals(Authenticator.Result.Status.ERROR, result.getStatus());
388         assertEquals("BAD_URL;\tRequest not allowed to access URL: {0};\t[\"/jira/secure/DeleteProject.jspa\"]", result.getMessage());
389         assertNull(result.getUser());
390     }
391 }