• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2022 Google LLC
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are
6  * met:
7  *
8  *    * Redistributions of source code must retain the above copyright
9  * notice, this list of conditions and the following disclaimer.
10  *    * Redistributions in binary form must reproduce the above
11  * copyright notice, this list of conditions and the following disclaimer
12  * in the documentation and/or other materials provided with the
13  * distribution.
14  *
15  *    * Neither the name of Google LLC nor the names of its
16  * contributors may be used to endorse or promote products derived from
17  * this software without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30  */
31 
32 package com.google.auth.oauth2;
33 
34 import static org.junit.Assert.assertEquals;
35 import static org.junit.Assert.assertFalse;
36 import static org.junit.Assert.assertNull;
37 import static org.junit.Assert.assertTrue;
38 import static org.junit.Assert.fail;
39 
40 import com.google.api.client.json.GenericJson;
41 import java.io.IOException;
42 import java.math.BigDecimal;
43 import java.time.Instant;
44 import org.junit.Test;
45 
46 /** Tests for {@link ExecutableResponse}. */
47 public class ExecutableResponseTest {
48 
49   private static final String TOKEN_TYPE_OIDC = "urn:ietf:params:oauth:token-type:id_token";
50   private static final String TOKEN_TYPE_SAML = "urn:ietf:params:oauth:token-type:saml2";
51   private static final String ID_TOKEN = "header.payload.signature";
52   private static final String SAML_RESPONSE = "samlResponse";
53 
54   private static final int EXECUTABLE_SUPPORTED_MAX_VERSION = 1;
55   private static final int EXPIRATION_DURATION = 3600;
56 
57   @Test
constructor_successOidcResponse()58   public void constructor_successOidcResponse() throws IOException {
59     ExecutableResponse response = new ExecutableResponse(buildOidcResponse());
60 
61     assertTrue(response.isSuccessful());
62     assertTrue(response.isValid());
63     assertEquals(EXECUTABLE_SUPPORTED_MAX_VERSION, response.getVersion());
64     assertEquals(TOKEN_TYPE_OIDC, response.getTokenType());
65     assertEquals(ID_TOKEN, response.getSubjectToken());
66     assertTrue(
67         Instant.now().getEpochSecond() + EXPIRATION_DURATION == response.getExpirationTime());
68   }
69 
70   @Test
constructor_successOidcResponseMissingExpirationTimeField_notExpired()71   public void constructor_successOidcResponseMissingExpirationTimeField_notExpired()
72       throws IOException {
73     GenericJson jsonResponse = buildOidcResponse();
74     jsonResponse.remove("expiration_time");
75 
76     ExecutableResponse response = new ExecutableResponse(jsonResponse);
77 
78     assertTrue(response.isSuccessful());
79     assertTrue(response.isValid());
80     assertFalse(response.isExpired());
81     assertEquals(EXECUTABLE_SUPPORTED_MAX_VERSION, response.getVersion());
82     assertEquals(TOKEN_TYPE_OIDC, response.getTokenType());
83     assertEquals(ID_TOKEN, response.getSubjectToken());
84     assertNull(response.getExpirationTime());
85   }
86 
87   @Test
constructor_successSamlResponse()88   public void constructor_successSamlResponse() throws IOException {
89     ExecutableResponse response = new ExecutableResponse(buildSamlResponse());
90 
91     assertTrue(response.isSuccessful());
92     assertTrue(response.isValid());
93     assertEquals(EXECUTABLE_SUPPORTED_MAX_VERSION, response.getVersion());
94     assertEquals(TOKEN_TYPE_SAML, response.getTokenType());
95     assertEquals(SAML_RESPONSE, response.getSubjectToken());
96     assertTrue(
97         Instant.now().getEpochSecond() + EXPIRATION_DURATION == response.getExpirationTime());
98   }
99 
100   @Test
constructor_successSamlResponseMissingExpirationTimeField_notExpired()101   public void constructor_successSamlResponseMissingExpirationTimeField_notExpired()
102       throws IOException {
103     GenericJson jsonResponse = buildSamlResponse();
104     jsonResponse.remove("expiration_time");
105 
106     ExecutableResponse response = new ExecutableResponse(jsonResponse);
107 
108     assertTrue(response.isSuccessful());
109     assertTrue(response.isValid());
110     assertFalse(response.isExpired());
111     assertEquals(EXECUTABLE_SUPPORTED_MAX_VERSION, response.getVersion());
112     assertEquals(TOKEN_TYPE_SAML, response.getTokenType());
113     assertEquals(SAML_RESPONSE, response.getSubjectToken());
114     assertNull(response.getExpirationTime());
115   }
116 
117   @Test
constructor_validErrorResponse()118   public void constructor_validErrorResponse() throws IOException {
119     ExecutableResponse response = new ExecutableResponse(buildErrorResponse());
120 
121     assertFalse(response.isSuccessful());
122     assertFalse(response.isValid());
123     assertFalse(response.isExpired());
124     assertNull(response.getSubjectToken());
125     assertNull(response.getTokenType());
126     assertNull(response.getExpirationTime());
127     assertEquals(EXECUTABLE_SUPPORTED_MAX_VERSION, response.getVersion());
128     assertEquals("401", response.getErrorCode());
129     assertEquals("Caller not authorized.", response.getErrorMessage());
130   }
131 
132   @Test
constructor_errorResponseMissingCode_throws()133   public void constructor_errorResponseMissingCode_throws() throws IOException {
134     GenericJson jsonResponse = buildErrorResponse();
135 
136     Object[] values = new Object[] {null, ""};
137     for (Object value : values) {
138       jsonResponse.put("code", value);
139       try {
140         new ExecutableResponse(jsonResponse);
141         fail("Should not be able to continue without exception.");
142       } catch (PluggableAuthException exception) {
143         assertEquals(
144             "Error code INVALID_EXECUTABLE_RESPONSE: The executable response must contain "
145                 + "`error` and `message` fields when unsuccessful.",
146             exception.getMessage());
147       }
148     }
149   }
150 
151   @Test
constructor_errorResponseMissingMessage_throws()152   public void constructor_errorResponseMissingMessage_throws() throws IOException {
153     GenericJson jsonResponse = buildErrorResponse();
154 
155     Object[] values = new Object[] {null, ""};
156     for (Object value : values) {
157       jsonResponse.put("message", value);
158 
159       try {
160         new ExecutableResponse(jsonResponse);
161         fail("Should not be able to continue without exception.");
162       } catch (PluggableAuthException exception) {
163         assertEquals(
164             "Error code INVALID_EXECUTABLE_RESPONSE: The executable response must contain "
165                 + "`error` and `message` fields when unsuccessful.",
166             exception.getMessage());
167       }
168     }
169   }
170 
171   @Test
constructor_successResponseMissingVersionField_throws()172   public void constructor_successResponseMissingVersionField_throws() throws IOException {
173     GenericJson jsonResponse = buildOidcResponse();
174     jsonResponse.remove("version");
175 
176     try {
177       new ExecutableResponse(jsonResponse);
178       fail("Should not be able to continue without exception.");
179     } catch (PluggableAuthException exception) {
180       assertEquals(
181           "Error code INVALID_EXECUTABLE_RESPONSE: The executable response is missing the "
182               + "`version` field.",
183           exception.getMessage());
184     }
185   }
186 
187   @Test
constructor_successResponseMissingSuccessField_throws()188   public void constructor_successResponseMissingSuccessField_throws() throws Exception {
189     GenericJson jsonResponse = buildOidcResponse();
190     jsonResponse.remove("success");
191 
192     try {
193       new ExecutableResponse(jsonResponse);
194       fail("Should not be able to continue without exception.");
195     } catch (PluggableAuthException exception) {
196       assertEquals(
197           "Error code INVALID_EXECUTABLE_RESPONSE: The executable response is missing the "
198               + "`success` field.",
199           exception.getMessage());
200     }
201   }
202 
203   @Test
constructor_successResponseMissingTokenTypeField_throws()204   public void constructor_successResponseMissingTokenTypeField_throws() throws IOException {
205     GenericJson jsonResponse = buildOidcResponse();
206     jsonResponse.remove("token_type");
207 
208     try {
209       new ExecutableResponse(jsonResponse);
210       fail("Should not be able to continue without exception.");
211     } catch (PluggableAuthException exception) {
212       assertEquals(
213           "Error code INVALID_EXECUTABLE_RESPONSE: The executable response is missing the "
214               + "`token_type` field.",
215           exception.getMessage());
216     }
217   }
218 
219   @Test
constructor_samlResponseMissingSubjectToken_throws()220   public void constructor_samlResponseMissingSubjectToken_throws() throws IOException {
221     GenericJson jsonResponse = buildSamlResponse();
222 
223     Object[] values = new Object[] {null, ""};
224     for (Object value : values) {
225       jsonResponse.put("saml_response", value);
226 
227       try {
228         new ExecutableResponse(jsonResponse);
229         fail("Should not be able to continue without exception.");
230       } catch (PluggableAuthException exception) {
231         assertEquals(
232             "Error code INVALID_EXECUTABLE_RESPONSE: The executable response does not "
233                 + "contain a valid token.",
234             exception.getMessage());
235       }
236     }
237   }
238 
239   @Test
constructor_oidcResponseMissingSubjectToken_throws()240   public void constructor_oidcResponseMissingSubjectToken_throws() throws IOException {
241     GenericJson jsonResponse = buildOidcResponse();
242 
243     Object[] values = new Object[] {null, ""};
244     for (Object value : values) {
245       jsonResponse.put("id_token", value);
246 
247       try {
248         new ExecutableResponse(jsonResponse);
249         fail("Should not be able to continue without exception.");
250       } catch (PluggableAuthException exception) {
251         assertEquals(
252             "Error code INVALID_EXECUTABLE_RESPONSE: The executable response does not "
253                 + "contain a valid token.",
254             exception.getMessage());
255       }
256     }
257   }
258 
259   @Test
isExpired()260   public void isExpired() throws IOException {
261     GenericJson jsonResponse = buildOidcResponse();
262 
263     BigDecimal[] values =
264         new BigDecimal[] {
265           BigDecimal.valueOf(Instant.now().getEpochSecond() - 1000),
266           BigDecimal.valueOf(Instant.now().getEpochSecond() + 1000)
267         };
268     boolean[] expectedResults = new boolean[] {true, false};
269 
270     for (int i = 0; i < values.length; i++) {
271       jsonResponse.put("expiration_time", values[i]);
272 
273       ExecutableResponse response = new ExecutableResponse(jsonResponse);
274 
275       assertEquals(expectedResults[i], response.isExpired());
276     }
277   }
278 
buildOidcResponse()279   private static GenericJson buildOidcResponse() {
280     GenericJson json = new GenericJson();
281     json.setFactory(OAuth2Utils.JSON_FACTORY);
282     json.put("version", EXECUTABLE_SUPPORTED_MAX_VERSION);
283     json.put("success", true);
284     json.put("token_type", TOKEN_TYPE_OIDC);
285     json.put("id_token", ID_TOKEN);
286     json.put("expiration_time", Instant.now().getEpochSecond() + EXPIRATION_DURATION);
287     return json;
288   }
289 
buildSamlResponse()290   private static GenericJson buildSamlResponse() {
291     GenericJson json = new GenericJson();
292     json.setFactory(OAuth2Utils.JSON_FACTORY);
293     json.put("version", EXECUTABLE_SUPPORTED_MAX_VERSION);
294     json.put("success", true);
295     json.put("token_type", TOKEN_TYPE_SAML);
296     json.put("saml_response", "samlResponse");
297     json.put("expiration_time", Instant.now().getEpochSecond() + EXPIRATION_DURATION);
298     return json;
299   }
300 
buildErrorResponse()301   private static GenericJson buildErrorResponse() {
302     GenericJson json = new GenericJson();
303     json.setFactory(OAuth2Utils.JSON_FACTORY);
304     json.put("version", EXECUTABLE_SUPPORTED_MAX_VERSION);
305     json.put("success", false);
306     json.put("code", "401");
307     json.put("message", "Caller not authorized.");
308     return json;
309   }
310 }
311