Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,23 @@ public HttpProvider.HttpRequestAuthorizer getClientAuthorizer() {
* Builds a new {@link ClientAssertionCredentialsGrantRequest} with a freshly
* signed JWT assertion.
*
* <p>
* {@code scope} is baked into this provider at construction time because it is a
* configuration-time concern (which project this provider is configured for).
*
* <p>
* {@code resource} (RFC 8707) is intentionally NOT set here. It is a per-request
* parameter identifying the target resource server for which the token is intended.
* RFC 8707 anticipates callers varying {@code resource} per call, so it belongs on
* the request, not the provider. Callers should set it on the returned request:
* <pre>
* {@code
* AccessTokenRequest req = provider.getNewAccessTokenRequest();
* req.setResource(Arrays.asList("https://api.example.com"));
* tokenEndpoint.requestToken(req);
* }
* </pre>
*
* {@inheritDoc}
*/
@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -343,10 +343,12 @@ private void addApacheRequestEntity(HttpRequestBase apacheRequest,
String key = entry.getKey();
List<String> valueList = entry.getValue();
if (null != key && null != valueList && valueList.size() > 0) {
String value = valueList.get(0);
if (null != value) {
NameValuePair nameValuePair = new BasicNameValuePair(key, value);
parameters.add(nameValuePair);
// A key may have multiple values to support repeated form parameters
// (e.g. RFC 8707 resource=uri1&resource=uri2).
for (String value : valueList) {
if (null != value) {
parameters.add(new BasicNameValuePair(key, value));
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package com.here.account.oauth2;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
Expand Down Expand Up @@ -57,6 +58,13 @@ public abstract class AccessTokenRequest implements OlpHttpMessage {
*/
protected static final String SCOPE_JSON = "scope";
protected static final String SCOPE_FORM = "scope";

/**
* resource; the parameter name for RFC 8707 resource indicators when conveyed in a form body.
* No JSON equivalent — the SDK always sends token requests as application/x-www-form-urlencoded,
* which also aligns with RFC 8707 §2.
*/
protected static final String RESOURCE_FORM = "resource";

private final String grantType;

Expand All @@ -70,6 +78,7 @@ public abstract class AccessTokenRequest implements OlpHttpMessage {
private Long expiresIn;

private String scope;
private List<String> resource;
private transient Map<String, String> additionalHeaders = null;
private transient String correlationId = null;

Expand Down Expand Up @@ -207,15 +216,40 @@ public String toJson() {
}

/**
* Converts this request, to its formParams Map representation.
*
* @return the formParams, for use with application/x-www-form-urlencoded bodies.
* Get the RFC 8707 resource indicator URIs for this token request.
*
* @return the list of resource URIs, or null if not set
*/
public List<String> getResource() {
return resource;
}

/**
* Set the RFC 8707 resource indicator URIs for this token request.
* Each value must be an absolute URI per RFC 8707 §2. Query components
* SHOULD NOT be included; fragments MUST NOT be included.
* Sent as repeated {@code resource=} form parameters in the
* {@code application/x-www-form-urlencoded} POST body — not as URL query parameters.
*
* <p>No client-side URI validation is performed. Validation is the authorization
* server's responsibility per RFC 8707 §2; client-side checks would duplicate
* server logic and risk divergence.
*
* @param resource list of absolute resource URIs
* @return this
* @see <a href="https://www.rfc-editor.org/rfc/rfc8707">RFC 8707</a>
*/
public AccessTokenRequest setResource(List<String> resource) {
this.resource = resource;
return this;
}

public Map<String, List<String>> toFormParams() {
Map<String, List<String>> formParams = new HashMap<String, List<String>>();
addFormParam(formParams, GRANT_TYPE_FORM, getGrantType());
addFormParam(formParams, EXPIRES_IN_FORM, getExpiresIn());
addFormParam(formParams, SCOPE_FORM, getScope());
addFormParams(formParams, RESOURCE_FORM, getResource());
return formParams;
}

Expand All @@ -233,4 +267,19 @@ protected final static void addFormParam(Map<String, List<String>> formParams, S
formParams.put(name, Collections.singletonList(value.toString()));
}
}

/**
* Adds a multi-value form parameter entry to the given map.
* A defensive copy of {@code values} is stored.
* No-op if any of {@code formParams}, {@code name}, or {@code values} is null or empty.
*
* @param formParams the map to populate; must not be null
* @param name the parameter name; must not be null
* @param values the parameter values; must not be null or empty
*/
protected final static void addFormParams(Map<String, List<String>> formParams, String name, List<String> values) {
if (null != formParams && null != name && null != values && !values.isEmpty()) {
formParams.put(name, new ArrayList<>(values));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -82,4 +82,13 @@ public Map<String, List<String>> toFormParams() {
addFormParam(formParams, CLIENT_ASSERTION_FORM, clientAssertion);
return formParams;
}

/**
* {@inheritDoc}
*/
@Override
public ClientAssertionCredentialsGrantRequest setResource(List<String> resource) {
super.setResource(resource);
return this;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
*/
package com.here.account.oauth2;

import java.util.List;

/**
* An {@link AccessTokenRequest} for grant_type=client_credentials.
*
Expand All @@ -37,5 +39,14 @@ public ClientCredentialsGrantRequest setExpiresIn(Long expiresIn) {
super.setExpiresIn(expiresIn);
return this;
}

/**
* {@inheritDoc}
*/
@Override
public ClientCredentialsGrantRequest setResource(List<String> resource) {
super.setResource(resource);
return this;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -570,7 +570,9 @@ public Fresh<AccessTokenResponse> requestAutoRefreshingToken(AccessTokenRequest
return requestAutoRefreshingToken(() -> {
return new ClientCredentialsGrantRequest()
.setExpiresIn(request.getExpiresIn())
.setScope(request.getScope());
.setScope(request.getScope())
// null if not set; addFormParams() no-op for null
.setResource(request.getResource());
Comment thread
spaltis marked this conversation as resolved.
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,28 @@ public void test_formParamsPut_null() throws NoSuchFieldException, SecurityExcep
assertTrue("httpEntity was expected null, actual "+httpEntity, null == httpEntity);
}

@Test
public void test_formParams_multiValue_repeatedParams() throws Exception {
formParams = new HashMap<String, List<String>>();
formParams.put("grant_type", Collections.singletonList("client_credentials"));
formParams.put("resource", Arrays.asList("https://api.example.com", "https://data.example.com"));
httpRequest = httpProvider.getRequest(httpRequestAuthorizer, "POST", url, formParams);
HttpRequestBase httpRequestBase = getHttpRequestBase();
HttpPost httpPost = (HttpPost) httpRequestBase;
HttpEntity httpEntity = httpPost.getEntity();
assertNotNull("httpEntity was null", httpEntity);
String body = org.apache.http.util.EntityUtils.toString(httpEntity);
long resourceCount = 0;
for (String part : body.split("&")) {
if (part.startsWith("resource=")) resourceCount++;
}
assertEquals("expected 2 resource= params in: " + body, 2, resourceCount);
assertTrue("body should contain resource=https%3A%2F%2Fapi.example.com: " + body,
body.contains("resource=https%3A%2F%2Fapi.example.com"));
assertTrue("body should contain resource=https%3A%2F%2Fdata.example.com: " + body,
body.contains("resource=https%3A%2F%2Fdata.example.com"));
}


@Test
public void test_methodDoesntSupportJson() throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
Expand Down Expand Up @@ -254,6 +255,24 @@ public void test_getFormBody_second2() throws UnsupportedEncodingException {
);
}

@Test
public void test_getFormBody_repeatedResourceParams() throws UnsupportedEncodingException {
Map<String, List<String>> formParams = new HashMap<String, List<String>>();
formParams.put("grant_type", Collections.singletonList("client_credentials"));
formParams.put("resource", Arrays.asList("https://api.example.com", "https://data.example.com"));
byte[] formBody = JavaHttpProvider.getFormBody(formParams);
String body = new String(formBody, "UTF-8");
long resourceCount = 0;
for (String part : body.split("&")) {
if (part.startsWith("resource=")) resourceCount++;
}
assertEquals("expected 2 resource= params in: " + body, 2, resourceCount);
assertTrue("body should contain resource=https%3A%2F%2Fapi.example.com: " + body,
body.contains("resource=https%3A%2F%2Fapi.example.com"));
assertTrue("body should contain resource=https%3A%2F%2Fdata.example.com: " + body,
body.contains("resource=https%3A%2F%2Fdata.example.com"));
}


@Test
public void test_404() throws HttpException, IOException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@

import org.junit.Test;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;

Expand Down Expand Up @@ -89,4 +91,27 @@ public void testConstructor_rejectsNull() {
public void testConstructor_rejectsEmpty() {
new ClientAssertionCredentialsGrantRequest("");
}

@Test
public void testFormParams_singleResource() {
ClientAssertionCredentialsGrantRequest request = new ClientAssertionCredentialsGrantRequest(FAKE_JWT)
.setResource(Collections.singletonList("https://example.com/api"));
Map<String, List<String>> formParams = request.toFormParams();
assertEquals(Collections.singletonList("https://example.com/api"), formParams.get("resource"));
}

@Test
public void testFormParams_multipleResources() {
List<String> resources = Arrays.asList("https://api.example.com", "https://data.example.com");
ClientAssertionCredentialsGrantRequest request = new ClientAssertionCredentialsGrantRequest(FAKE_JWT)
.setResource(resources);
Map<String, List<String>> formParams = request.toFormParams();
assertEquals(resources, formParams.get("resource"));
}

@Test
public void testFormParams_noResource() {
ClientAssertionCredentialsGrantRequest request = new ClientAssertionCredentialsGrantRequest(FAKE_JWT);
assertNull(request.toFormParams().get("resource"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,13 @@
*/
package com.here.account.oauth2;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
Expand Down Expand Up @@ -81,6 +84,75 @@ public void test_ClientCredentialsGrantRequest_form_expiresIn() throws IOExcepti
}


@Test
public void test_ClientCredentialsGrantRequest_form_singleResource() {
ClientCredentialsGrantRequest request = new ClientCredentialsGrantRequest()
.setResource(Collections.singletonList("https://example.com/api"));
Map<String, List<String>> form = request.toFormParams();
assertEquals(Collections.singletonList("https://example.com/api"), form.get("resource"));
}

@Test
public void test_ClientCredentialsGrantRequest_form_multipleResources() {
List<String> resources = Arrays.asList("https://api.example.com", "https://data.example.com");
ClientCredentialsGrantRequest request = new ClientCredentialsGrantRequest()
.setResource(resources);
Map<String, List<String>> form = request.toFormParams();
assertEquals(resources, form.get("resource"));
}

@Test
public void test_ClientCredentialsGrantRequest_form_noResource() {
ClientCredentialsGrantRequest request = new ClientCredentialsGrantRequest();
Map<String, List<String>> form = request.toFormParams();
assertNull(form.get("resource"));
}

@Test
public void test_ClientCredentialsGrantRequest_getResource() {
List<String> resources = Collections.singletonList("https://example.com");
ClientCredentialsGrantRequest request = new ClientCredentialsGrantRequest()
.setResource(resources);
assertEquals(resources, request.getResource());
}

@Test
public void test_addFormParams_nullFormParams_noOp() {
// must not throw
AccessTokenRequest.addFormParams(null, "resource", Collections.singletonList("https://example.com"));
}

@Test
public void test_addFormParams_nullName_noOp() {
Map<String, List<String>> formParams = new HashMap<String, List<String>>();
AccessTokenRequest.addFormParams(formParams, null, Collections.singletonList("https://example.com"));
assertTrue(formParams.isEmpty());
}

@Test
public void test_addFormParams_nullValues_noOp() {
Map<String, List<String>> formParams = new HashMap<String, List<String>>();
AccessTokenRequest.addFormParams(formParams, "resource", null);
assertTrue(formParams.isEmpty());
}

@Test
public void test_addFormParams_emptyValues_noOp() {
Map<String, List<String>> formParams = new HashMap<String, List<String>>();
AccessTokenRequest.addFormParams(formParams, "resource", Collections.<String>emptyList());
assertTrue(formParams.isEmpty());
}

@Test
public void test_addFormParams_defensiveCopy() {
Map<String, List<String>> formParams = new HashMap<String, List<String>>();
List<String> values = new java.util.ArrayList<String>(Collections.singletonList("https://example.com"));
AccessTokenRequest.addFormParams(formParams, "resource", values);
values.add("https://other.com");
assertEquals(1, formParams.get("resource").size());
}


private Map<String, Object> toMap(String json) throws IOException {
byte[] bytes = json.getBytes(OAuthConstants.UTF_8_CHARSET);
ByteArrayInputStream jsonInputStream = null;
Expand Down
Loading
Loading