1   package com.atlassian.security.auth.trustedapps.filter;
2   
3   import java.security.KeyPair;
4   import java.security.KeyPairGenerator;
5   import java.security.NoSuchAlgorithmException;
6   import java.security.Principal;
7   
8   import javax.servlet.http.HttpServletRequest;
9   import javax.servlet.http.HttpServletResponse;
10  
11  import com.atlassian.security.auth.trustedapps.ApplicationCertificate;
12  import com.atlassian.security.auth.trustedapps.BouncyCastleEncryptionProvider;
13  import com.atlassian.security.auth.trustedapps.DefaultTrustedApplication;
14  import com.atlassian.security.auth.trustedapps.EncryptedCertificate;
15  import com.atlassian.security.auth.trustedapps.EncryptionProvider;
16  import com.atlassian.security.auth.trustedapps.RequestConditions;
17  import com.atlassian.security.auth.trustedapps.TrustedApplication;
18  import com.atlassian.security.auth.trustedapps.TrustedApplicationUtils;
19  import com.atlassian.security.auth.trustedapps.TrustedApplicationsManager;
20  import com.atlassian.security.auth.trustedapps.UserResolver;
21  import com.atlassian.security.auth.trustedapps.filter.Authenticator.Result;
22  import com.atlassian.security.auth.trustedapps.request.TrustedRequest;
23  import com.atlassian.security.auth.trustedapps.request.commonshttpclient.CommonsHttpClientTrustedRequest;
24  
25  import org.apache.commons.httpclient.Header;
26  import org.apache.commons.httpclient.HttpMethod;
27  import org.apache.commons.httpclient.URIException;
28  import org.apache.commons.httpclient.methods.GetMethod;
29  import org.junit.Before;
30  import org.junit.Test;
31  import org.junit.runner.RunWith;
32  import org.mockito.Mock;
33  import org.mockito.Mockito;
34  import org.mockito.invocation.InvocationOnMock;
35  import org.mockito.runners.MockitoJUnitRunner;
36  import org.mockito.stubbing.Answer;
37  
38  import static org.junit.Assert.assertEquals;
39  import static org.junit.Assert.assertNotNull;
40  import static org.junit.Assert.assertNull;
41  import static org.mockito.Mockito.mock;
42  import static org.mockito.Mockito.when;
43  
44  /**
45   * Integration tests for creating and processing requests. End to end tests, as far as possible,
46   * to make it easier to understand the impact of changes to the security protocol.
47   */
48  @RunWith(MockitoJUnitRunner.class)
49  public class TestAuthenticationIntegration
50  {
51      /* Crypto */
52      private KeyPair keyPair;
53      
54      /* Trusted Apps */
55      @Mock public TrustedApplicationsManager appManager;
56      @Mock public AuthenticationController authenticationController;
57      @Mock public TrustedApplication trustedApplication;
58  
59      @Before
60      public void cryptoAndTrustedAppsMockBehaviour() throws NoSuchAlgorithmException
61      {
62          keyPair = generateSingleUseKeyPair();
63          
64          RequestConditions conditions = RequestConditions.builder().build();
65          trustedApplication = new DefaultTrustedApplication(keyPair.getPublic(), "appId", conditions);
66          
67          when(appManager.getTrustedApplication("appId")).thenReturn(trustedApplication);
68          ApplicationCertificate cert = mock(ApplicationCertificate.class);
69          when(cert.getUserName()).thenReturn("User");
70          
71          when(authenticationController.canLogin(Mockito.<Principal>anyObject(), Mockito.<HttpServletRequest>anyObject())).thenReturn(true);
72      }
73      
74      public static KeyPair generateSingleUseKeyPair() throws NoSuchAlgorithmException
75      {
76          KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
77          return kpg.genKeyPair();
78      }
79      
80      private EncryptedCertificate getEncryptedCertificate(String urlToSign)
81      {
82          EncryptionProvider provider;
83          provider = new BouncyCastleEncryptionProvider();
84          EncryptedCertificate certificate = provider.createEncryptedCertificate("user", keyPair.getPrivate(), "appId", urlToSign);
85          return certificate;
86      }
87      
88      public HttpServletRequest mockHttpServletRequestWithFieldsFromHttpMethod(HttpMethod method)
89          throws URIException
90      {
91          HttpServletRequest hrq = mock(HttpServletRequest.class);
92  
93          final String requestUrl = method.getURI().toString();
94          
95          when(hrq.getRequestURL()).thenAnswer(new Answer<StringBuffer>()
96          {
97              public StringBuffer answer(InvocationOnMock invocation) throws Throwable
98              {
99                  return new StringBuffer(requestUrl);
100             }
101         });
102         
103         String[] toMock = {
104                 "ID",
105                 "Cert",
106                 "Key",
107                 "Version",
108                 "Magic",
109                 "Signature"
110         };
111         
112         for (String f : toMock)
113         {
114             String hn = "X-Seraph-Trusted-App-" + f;
115             Header header = method.getRequestHeader(hn);
116             if (header != null) {
117                 when(hrq.getHeader(hn)).thenReturn(header.getValue());
118             }
119         }
120 
121         return hrq;
122     }
123     
124     public HttpMethod requestFor(String url) throws Exception
125     {
126         HttpMethod method = new GetMethod(url);
127         TrustedRequest request = new CommonsHttpClientTrustedRequest(method);
128         TrustedApplicationUtils.addRequestParameters(getEncryptedCertificate(url), request);
129         return method;
130     }
131     
132     @Test
133     public void validRequestIsAccepted() throws Exception
134     {
135         /* Trusted Apps framework classes */
136         DummyResolver resolver = new DummyResolver();
137         
138         HttpMethod method = requestFor("http://www.example.com/");
139         
140         TrustedApplicationFilterAuthenticator authenticator = new TrustedApplicationFilterAuthenticator(appManager, resolver, authenticationController);
141 
142         HttpServletRequest hrq = mockHttpServletRequestWithFieldsFromHttpMethod(method);
143         
144         
145         HttpServletResponse response = mock(HttpServletResponse.class);
146         Result result = authenticator.authenticate(hrq, response);
147         assertEquals("success", ((Object) result.getStatus()).toString());
148         assertEquals("Trusted Apps enlowercases usernames", "appId/user", result.getUser().getName());
149     }
150     
151     @Test
152     public void errorWhenNoTrustedAppsHeadersArePresent()
153     {
154         UserResolver resolver = null;
155         TrustedApplicationFilterAuthenticator authenticator = new TrustedApplicationFilterAuthenticator(appManager, resolver , authenticationController);
156         
157         HttpServletRequest hrq = mock(HttpServletRequest.class);
158         
159         HttpServletResponse response = mock(HttpServletResponse.class);
160         Result result = authenticator.authenticate(hrq, response);
161         assertEquals("no attempt", ((Object) result.getStatus()).toString());
162     }
163     
164     @Test
165     public void errorWhenKeyIsModified() throws Exception
166     {
167         HttpMethod method = requestFor("http://www.example.com/");
168         
169         method.setRequestHeader("X-Seraph-Trusted-App-Key", "XXXX");
170         
171         TrustedApplicationFilterAuthenticator authenticator = new TrustedApplicationFilterAuthenticator(appManager, null, authenticationController);
172 
173         HttpServletRequest hrq = mockHttpServletRequestWithFieldsFromHttpMethod(method);
174         
175         
176         HttpServletResponse response = mock(HttpServletResponse.class);
177         Result result = authenticator.authenticate(hrq, response);
178         assertEquals("error", ((Object) result.getStatus()).toString());
179     }
180     
181     @Test
182     public void errorWhenUserIsUnknown() throws Exception
183     {
184         UserResolver resolver = mock(UserResolver.class);
185 
186         HttpMethod method = requestFor("http://www.example.com/");
187         
188         TrustedApplicationFilterAuthenticator authenticator = new TrustedApplicationFilterAuthenticator(appManager, resolver, authenticationController);
189 
190         HttpServletRequest hrq = mockHttpServletRequestWithFieldsFromHttpMethod(method);
191         
192         
193         HttpServletResponse response = mock(HttpServletResponse.class);
194         Result result = authenticator.authenticate(hrq, response);
195         assertEquals("failed", ((Object) result.getStatus()).toString());
196     }
197     
198     @Test
199     public void noSignedUrlWhenSignatureIsNotProvided() throws Exception
200     {
201         /* Trusted Apps framework classes */
202         DummyResolver resolver = new DummyResolver();
203         
204         HttpMethod method = requestFor("http://www.example.com/");
205         method.setRequestHeader("X-Seraph-Trusted-App-Version", "1");
206         method.removeRequestHeader("X-Seraph-Trusted-App-Signature");
207         
208         TrustedApplicationFilterAuthenticator authenticator = new TrustedApplicationFilterAuthenticator(appManager, resolver, authenticationController);
209 
210         HttpServletRequest hrq = mockHttpServletRequestWithFieldsFromHttpMethod(method);
211         
212         
213         HttpServletResponse response = mock(HttpServletResponse.class);
214         Result result = authenticator.authenticate(hrq, response);
215         assertEquals("success", ((Object) result.getStatus()).toString());
216         assertNull("No signed URL as no signature provided", ((Result.Success) result).getSignedUrl());
217     }
218     
219     @Test
220     public void errorWhenSignatureIsInvalid() throws Exception
221     {
222         /* Trusted Apps framework classes */
223         DummyResolver resolver = new DummyResolver();
224         
225         HttpMethod method = requestFor("http://www.example.com/");
226         
227         TrustedApplicationFilterAuthenticator authenticator =
228             new TrustedApplicationFilterAuthenticator(appManager, resolver, authenticationController);
229 
230         method.setRequestHeader("X-Seraph-Trusted-App-Signature", "XXXX");
231         HttpServletRequest hrq = mockHttpServletRequestWithFieldsFromHttpMethod(method);
232         
233         
234         HttpServletResponse response = mock(HttpServletResponse.class);
235         Result result = authenticator.authenticate(hrq, response);
236         assertEquals("error", ((Object) result.getStatus()).toString());
237     }
238     
239     @Test
240     public void signedUrlIncludedInResultWhenSignatureIsValid() throws Exception
241     {
242         /* Trusted Apps framework classes */
243         DummyResolver resolver = new DummyResolver();
244         
245         HttpMethod method = requestFor("http://www.example.com/");
246         
247         TrustedApplicationFilterAuthenticator authenticator =
248             new TrustedApplicationFilterAuthenticator(appManager, resolver, authenticationController);
249 
250         HttpServletRequest hrq = mockHttpServletRequestWithFieldsFromHttpMethod(method);
251         assertNotNull(method.getRequestHeader("X-Seraph-Trusted-App-Signature"));
252         
253         HttpServletResponse response = mock(HttpServletResponse.class);
254         Result result = authenticator.authenticate(hrq, response);
255         assertEquals("success", ((Object) result.getStatus()).toString());
256         assertEquals("http://www.example.com/", ((Result.Success) result).getSignedUrl());
257     }
258     
259     @Test
260     public void signingADifferentUrlCausesAnError() throws Exception
261     {
262         /* Trusted Apps framework classes */
263         DummyResolver resolver = new DummyResolver();
264         
265         HttpMethod method = requestFor("http://www.example.com/");
266         
267         TrustedApplicationFilterAuthenticator authenticator =
268             new TrustedApplicationFilterAuthenticator(appManager, resolver, authenticationController);
269 
270         HttpServletRequest hrq = mockHttpServletRequestWithFieldsFromHttpMethod(method);
271         when(hrq.getRequestURL()).thenAnswer(new Answer<StringBuffer>(){
272            public StringBuffer answer(InvocationOnMock invocation) throws Throwable
273             {
274                return new StringBuffer("http://unexpected.hostname.invalid/");
275             } 
276         });
277         assertNotNull(method.getRequestHeader("X-Seraph-Trusted-App-Signature"));
278         
279         HttpServletResponse response = mock(HttpServletResponse.class);
280         Result result = authenticator.authenticate(hrq, response);
281         assertEquals("error", ((Object) result.getStatus()).toString());
282     }
283     
284     @Test
285     public void v2RequestWithoutSignatureIsAnError() throws Exception
286     {
287         DummyResolver resolver = new DummyResolver();
288         
289         HttpMethod method = requestFor("http://www.example.com/");
290         assertEquals("2", method.getRequestHeader("X-Seraph-Trusted-App-Version").getValue());
291         method.removeRequestHeader("X-Seraph-Trusted-App-Signature");
292 
293         TrustedApplicationFilterAuthenticator authenticator =
294             new TrustedApplicationFilterAuthenticator(appManager, resolver, authenticationController);
295 
296         HttpServletRequest hrq = mockHttpServletRequestWithFieldsFromHttpMethod(method);
297         
298         HttpServletResponse response = Mockito.mock(HttpServletResponse.class);
299         Result result = authenticator.authenticate(hrq, response);
300         assertEquals("error", ((Object) result.getStatus()).toString());
301     }
302     
303     static class DummyResolver implements UserResolver
304     {
305         public Principal resolve(ApplicationCertificate certificate)
306         {
307             Principal p = mock(Principal.class);
308             when(p.getName()).thenReturn(certificate.getApplicationID() + "/" + certificate.getUserName());
309             return p;
310         }
311     }
312 }