• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2021 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.assertNull;
36 import static org.junit.Assert.assertThrows;
37 
38 import com.google.api.client.http.HttpHeaders;
39 import com.google.api.client.testing.http.MockLowLevelHttpRequest;
40 import com.google.api.client.util.GenericData;
41 import com.google.auth.TestUtils;
42 import com.google.common.base.Joiner;
43 import java.io.IOException;
44 import java.util.Arrays;
45 import java.util.List;
46 import java.util.Map;
47 import org.junit.Before;
48 import org.junit.Test;
49 import org.junit.function.ThrowingRunnable;
50 import org.junit.runner.RunWith;
51 import org.junit.runners.JUnit4;
52 
53 /** Tests for {@link StsRequestHandler}. */
54 @RunWith(JUnit4.class)
55 public final class StsRequestHandlerTest {
56 
57   private static final String TOKEN_EXCHANGE_GRANT_TYPE =
58       "urn:ietf:params:oauth:grant-type:token-exchange";
59   private static final String CLOUD_PLATFORM_SCOPE =
60       "https://www.googleapis.com/auth/cloud-platform";
61   private static final String DEFAULT_REQUESTED_TOKEN_TYPE =
62       "urn:ietf:params:oauth:token-type:access_token";
63   private static final String TOKEN_URL = "https://sts.googleapis.com/v1/token";
64 
65   private MockStsTransport transport;
66 
67   @Before
setup()68   public void setup() {
69     transport = new MockStsTransport();
70   }
71 
72   @Test
exchangeToken()73   public void exchangeToken() throws IOException {
74     StsTokenExchangeRequest stsTokenExchangeRequest =
75         StsTokenExchangeRequest.newBuilder("credential", "subjectTokenType")
76             .setScopes(Arrays.asList(CLOUD_PLATFORM_SCOPE))
77             .build();
78 
79     StsRequestHandler requestHandler =
80         StsRequestHandler.newBuilder(
81                 TOKEN_URL, stsTokenExchangeRequest, transport.createRequestFactory())
82             .build();
83 
84     StsTokenExchangeResponse response = requestHandler.exchangeToken();
85 
86     // Validate response.
87     assertEquals(transport.getAccessToken(), response.getAccessToken().getTokenValue());
88     assertEquals(transport.getTokenType(), response.getTokenType());
89     assertEquals(transport.getIssuedTokenType(), response.getIssuedTokenType());
90     assertEquals(transport.getExpiresIn(), response.getExpiresInSeconds());
91 
92     // Validate request content.
93     GenericData expectedRequestContent =
94         new GenericData()
95             .set("grant_type", TOKEN_EXCHANGE_GRANT_TYPE)
96             .set("scope", CLOUD_PLATFORM_SCOPE)
97             .set("requested_token_type", DEFAULT_REQUESTED_TOKEN_TYPE)
98             .set("subject_token_type", stsTokenExchangeRequest.getSubjectTokenType())
99             .set("subject_token", stsTokenExchangeRequest.getSubjectToken());
100 
101     MockLowLevelHttpRequest request = transport.getRequest();
102     Map<String, String> actualRequestContent = TestUtils.parseQuery(request.getContentAsString());
103     assertEquals(expectedRequestContent.getUnknownKeys(), actualRequestContent);
104   }
105 
106   @Test
exchangeToken_withOptionalParams()107   public void exchangeToken_withOptionalParams() throws IOException {
108     // Return optional params scope and the refresh_token.
109     transport.addScopeSequence(Arrays.asList("scope1", "scope2", "scope3"));
110     transport.addRefreshTokenSequence("refreshToken");
111 
112     // Build the token exchange request.
113     StsTokenExchangeRequest stsTokenExchangeRequest =
114         StsTokenExchangeRequest.newBuilder("credential", "subjectTokenType")
115             .setAudience("audience")
116             .setResource("resource")
117             .setActingParty(new ActingParty("actorToken", "actorTokenType"))
118             .setRequestTokenType("requestedTokenType")
119             .setScopes(Arrays.asList("scope1", "scope2", "scope3"))
120             .build();
121 
122     HttpHeaders httpHeaders =
123         new HttpHeaders()
124             .setContentType("application/x-www-form-urlencoded")
125             .setAcceptEncoding("gzip")
126             .set("custom_header_key", "custom_header_value");
127 
128     StsRequestHandler requestHandler =
129         StsRequestHandler.newBuilder(
130                 TOKEN_URL, stsTokenExchangeRequest, transport.createRequestFactory())
131             .setHeaders(httpHeaders)
132             .setInternalOptions("internalOptions")
133             .build();
134 
135     StsTokenExchangeResponse response = requestHandler.exchangeToken();
136 
137     // Validate response.
138     assertEquals(transport.getAccessToken(), response.getAccessToken().getTokenValue());
139     assertEquals(transport.getTokenType(), response.getTokenType());
140     assertEquals(transport.getIssuedTokenType(), response.getIssuedTokenType());
141     assertEquals(transport.getExpiresIn(), response.getExpiresInSeconds());
142     assertEquals(Arrays.asList("scope1", "scope2", "scope3"), response.getScopes());
143     assertEquals("refreshToken", response.getRefreshToken());
144 
145     // Validate headers.
146     MockLowLevelHttpRequest request = transport.getRequest();
147     Map<String, List<String>> requestHeaders = request.getHeaders();
148     assertEquals("application/x-www-form-urlencoded", requestHeaders.get("content-type").get(0));
149     assertEquals("gzip", requestHeaders.get("accept-encoding").get(0));
150     assertEquals("custom_header_value", requestHeaders.get("custom_header_key").get(0));
151 
152     // Validate request content.
153     GenericData expectedRequestContent =
154         new GenericData()
155             .set("grant_type", TOKEN_EXCHANGE_GRANT_TYPE)
156             .set("scope", Joiner.on(' ').join(Arrays.asList("scope1", "scope2", "scope3")))
157             .set("options", "internalOptions")
158             .set("subject_token_type", stsTokenExchangeRequest.getSubjectTokenType())
159             .set("subject_token", stsTokenExchangeRequest.getSubjectToken())
160             .set("requested_token_type", stsTokenExchangeRequest.getRequestedTokenType())
161             .set("actor_token", stsTokenExchangeRequest.getActingParty().getActorToken())
162             .set("actor_token_type", stsTokenExchangeRequest.getActingParty().getActorTokenType())
163             .set("resource", stsTokenExchangeRequest.getResource())
164             .set("audience", stsTokenExchangeRequest.getAudience());
165 
166     Map<String, String> actualRequestContent = TestUtils.parseQuery(request.getContentAsString());
167     assertEquals(expectedRequestContent.getUnknownKeys(), actualRequestContent);
168   }
169 
170   @Test
exchangeToken_throwsException()171   public void exchangeToken_throwsException() throws IOException {
172     StsTokenExchangeRequest stsTokenExchangeRequest =
173         StsTokenExchangeRequest.newBuilder("credential", "subjectTokenType").build();
174 
175     final StsRequestHandler requestHandler =
176         StsRequestHandler.newBuilder(
177                 TOKEN_URL, stsTokenExchangeRequest, transport.createRequestFactory())
178             .build();
179 
180     transport.addResponseErrorSequence(
181         TestUtils.buildHttpResponseException(
182             "invalidRequest", /* errorDescription= */ null, /* errorUri= */ null));
183 
184     OAuthException e =
185         assertThrows(
186             OAuthException.class,
187             new ThrowingRunnable() {
188               @Override
189               public void run() throws Throwable {
190                 requestHandler.exchangeToken();
191               }
192             });
193 
194     assertEquals("invalidRequest", e.getErrorCode());
195     assertNull(e.getErrorDescription());
196     assertNull(e.getErrorUri());
197   }
198 
199   @Test
exchangeToken_withOptionalParams_throwsException()200   public void exchangeToken_withOptionalParams_throwsException() throws IOException {
201     StsTokenExchangeRequest stsTokenExchangeRequest =
202         StsTokenExchangeRequest.newBuilder("credential", "subjectTokenType").build();
203 
204     final StsRequestHandler requestHandler =
205         StsRequestHandler.newBuilder(
206                 TOKEN_URL, stsTokenExchangeRequest, transport.createRequestFactory())
207             .build();
208 
209     transport.addResponseErrorSequence(
210         TestUtils.buildHttpResponseException("invalidRequest", "errorDescription", "errorUri"));
211 
212     OAuthException e =
213         assertThrows(
214             OAuthException.class,
215             new ThrowingRunnable() {
216               @Override
217               public void run() throws Throwable {
218                 requestHandler.exchangeToken();
219               }
220             });
221 
222     assertEquals("invalidRequest", e.getErrorCode());
223     assertEquals("errorDescription", e.getErrorDescription());
224     assertEquals("errorUri", e.getErrorUri());
225   }
226 
227   @Test
exchangeToken_ioException()228   public void exchangeToken_ioException() {
229     StsTokenExchangeRequest stsTokenExchangeRequest =
230         StsTokenExchangeRequest.newBuilder("credential", "subjectTokenType").build();
231 
232     final StsRequestHandler requestHandler =
233         StsRequestHandler.newBuilder(
234                 TOKEN_URL, stsTokenExchangeRequest, transport.createRequestFactory())
235             .build();
236 
237     IOException e = new IOException();
238     transport.addResponseErrorSequence(e);
239 
240     IOException thrownException =
241         assertThrows(
242             IOException.class,
243             new ThrowingRunnable() {
244               @Override
245               public void run() throws Throwable {
246                 requestHandler.exchangeToken();
247               }
248             });
249     assertEquals(e, thrownException);
250   }
251 
252   @Test
exchangeToken_noExpiresInReturned()253   public void exchangeToken_noExpiresInReturned() throws IOException {
254     // Don't return expires in. This happens in the CAB flow when the subject token does not belong
255     // to a service account.
256     transport.setReturnExpiresIn(/* returnExpiresIn= */ false);
257 
258     StsTokenExchangeRequest stsTokenExchangeRequest =
259         StsTokenExchangeRequest.newBuilder("credential", "subjectTokenType")
260             .setScopes(Arrays.asList(CLOUD_PLATFORM_SCOPE))
261             .build();
262 
263     StsRequestHandler requestHandler =
264         StsRequestHandler.newBuilder(
265                 TOKEN_URL, stsTokenExchangeRequest, transport.createRequestFactory())
266             .build();
267 
268     StsTokenExchangeResponse response = requestHandler.exchangeToken();
269 
270     // Validate response.
271     assertEquals(transport.getAccessToken(), response.getAccessToken().getTokenValue());
272     assertNull(response.getAccessToken().getExpirationTime());
273 
274     assertEquals(transport.getTokenType(), response.getTokenType());
275     assertEquals(transport.getIssuedTokenType(), response.getIssuedTokenType());
276     assertNull(response.getExpiresInSeconds());
277   }
278 }
279