Skip to content

Commit

Permalink
oxAuth #830
Browse files Browse the repository at this point in the history
Client-specific access token expiration
  • Loading branch information
qbert2k committed Nov 29, 2018
1 parent bbf54b4 commit 1caf702
Show file tree
Hide file tree
Showing 10 changed files with 283 additions and 78 deletions.
Expand Up @@ -29,7 +29,7 @@
* @author Javier Rojas Blum
* @author Yuriy Zabrovarnyy
* @author Yuriy Movchan
* @version May 30, 2018
* @version November 28, 2018
*/
public class RegisterClient extends BaseClient<RegisterRequest, RegisterResponse> {

Expand Down Expand Up @@ -207,7 +207,6 @@ private RegisterResponse _exec() {
if (getRequest().getClientSecretExpiresAt() != null) {
requestBody.put(CLIENT_SECRET_EXPIRES_AT_.toString(), getRequest().getClientSecretExpiresAt().getTime());
}

if (getRequest().getFrontChannelLogoutSessionRequired() != null) {
requestBody.put(FRONT_CHANNEL_LOGOUT_SESSION_REQUIRED.getName(), getRequest().getFrontChannelLogoutSessionRequired());
}
Expand All @@ -217,6 +216,9 @@ private RegisterResponse _exec() {
if (getRequest().getAuthorizedOrigins() != null && !getRequest().getAuthorizedOrigins().isEmpty()) {
requestBody.put(AUTHORIZED_ORIGINS.toString(), new JSONArray(getRequest().getAuthorizedOrigins()));
}
if (getRequest().getAccessTokenLifetime() != null) {
requestBody.put(ACCESS_TOKEN_LIFETIME.toString(), getRequest().getAccessTokenLifetime());
}

if (getRequest().getScopes() != null && !getRequest().getScopes().isEmpty()) {
requestBody.put(SCOPES.toString(), new JSONArray(getRequest().getScopes()));
Expand Down
28 changes: 27 additions & 1 deletion Client/src/main/java/org/xdi/oxauth/client/RegisterRequest.java
Expand Up @@ -32,7 +32,7 @@
*
* @author Javier Rojas Blum
* @author Yuriy Zabrovarnyy
* @version May 30, 2018
* @version November 28, 2018
*/
public class RegisterRequest extends BaseRequest {

Expand Down Expand Up @@ -75,6 +75,7 @@ public class RegisterRequest extends BaseRequest {
private List<String> postLogoutRedirectUris;
private List<String> requestUris;
private List<String> authorizedOrigins;
private Integer accessTokenLifetime;

/**
* @deprecated This param will be removed in a future version because the correct is 'scope' not 'scopes', see (rfc7591).
Expand Down Expand Up @@ -904,6 +905,23 @@ public void setClaims(List<String> claims) {
this.claims = claims;
}

/**
* Returns the Client-specific access token expiration.
*
* @return The Client-specific access token expiration.
*/
public Integer getAccessTokenLifetime() {
return accessTokenLifetime;
}
/**
* Sets the Client-specific access token expiration (in seconds). Set it to Null or Zero to use the system default value.
*
* @param accessTokenLifetime The Client-specific access token expiration.
*/
public void setAccessTokenLifetime(Integer accessTokenLifetime) {
this.accessTokenLifetime = accessTokenLifetime;
}

public String getHttpMethod() {
return httpMethod;
}
Expand Down Expand Up @@ -1064,6 +1082,9 @@ public Map<String, String> getParameters() {
if (clientSecretExpiresAt != null) {
parameters.put(CLIENT_SECRET_EXPIRES_AT_.toString(), Long.toString(clientSecretExpiresAt.getTime()));
}
if (accessTokenLifetime != null) {
parameters.put(ACCESS_TOKEN_LIFETIME.toString(), accessTokenLifetime.toString());
}

// Custom params
if (customAttributes != null && !customAttributes.isEmpty()) {
Expand Down Expand Up @@ -1222,6 +1243,8 @@ public static RegisterRequest fromJson(String p_json, boolean authorizationReque
result.setRequireAuthTime(requestObject.has(REQUIRE_AUTH_TIME.toString()) && requestObject.getBoolean(REQUIRE_AUTH_TIME.toString()));
result.setFrontChannelLogoutUris(frontChannelLogoutUris);
result.setFrontChannelLogoutSessionRequired(requestObject.optBoolean(FRONT_CHANNEL_LOGOUT_SESSION_REQUIRED.toString()));
result.setAccessTokenLifetime(requestObject.has(ACCESS_TOKEN_LIFETIME.toString()) ?
requestObject.getInt(ACCESS_TOKEN_LIFETIME.toString()) : null);
result.setDefaultMaxAge(requestObject.has(DEFAULT_MAX_AGE.toString()) ?
requestObject.getInt(DEFAULT_MAX_AGE.toString()) : null);
result.setAccessTokenAsJwt(requestObject.optBoolean(ACCESS_TOKEN_AS_JWT.toString()));
Expand Down Expand Up @@ -1401,6 +1424,9 @@ public JSONObject getJSONParameters() throws JSONException {
if (clientSecretExpiresAt != null) {
parameters.put(CLIENT_SECRET_EXPIRES_AT_.toString(), clientSecretExpiresAt.getTime());
}
if (accessTokenLifetime != null) {
parameters.put(ACCESS_TOKEN_LIFETIME.toString(), accessTokenLifetime);
}
// Custom params
if (customAttributes != null && !customAttributes.isEmpty()) {
for (Map.Entry<String, String> entry : customAttributes.entrySet()) {
Expand Down
@@ -0,0 +1,154 @@
/*
* oxAuth is available under the MIT License (2008). See http://opensource.org/licenses/MIT for full text.
*
* Copyright (c) 2014, Gluu
*/

package org.xdi.oxauth.ws.rs;

import org.testng.annotations.Parameters;
import org.testng.annotations.Test;
import org.xdi.oxauth.BaseTest;
import org.xdi.oxauth.client.*;
import org.xdi.oxauth.model.common.AuthenticationMethod;
import org.xdi.oxauth.model.common.GrantType;
import org.xdi.oxauth.model.common.ResponseType;
import org.xdi.oxauth.model.common.SubjectType;
import org.xdi.oxauth.model.jwt.JwtClaimName;
import org.xdi.oxauth.model.register.ApplicationType;
import org.xdi.oxauth.model.util.StringUtils;

import java.util.Arrays;
import java.util.List;
import java.util.UUID;

import static org.testng.Assert.*;

/**
* @author Javier Rojas Blum
* @version November 28, 2018
*/
public class ClientSpecificAccessTokenExpiration extends BaseTest {

@Parameters({"userId", "userSecret", "redirectUris", "redirectUri", "sectorIdentifierUri"})
@Test
public void authorizationCodeFlow(
final String userId, final String userSecret, final String redirectUris, final String redirectUri,
final String sectorIdentifierUri) throws Exception {
showTitle("authorizationCodeFlow");

List<ResponseType> responseTypes = Arrays.asList(
ResponseType.CODE,
ResponseType.ID_TOKEN);
List<String> scopes = Arrays.asList("openid", "profile", "address", "email", "phone", "user_name");

// 1. Register client
RegisterRequest registerRequest = new RegisterRequest(ApplicationType.WEB, "oxAuth test app",
StringUtils.spaceSeparatedToList(redirectUris));
registerRequest.setResponseTypes(responseTypes);
registerRequest.setScope(scopes);
registerRequest.setSubjectType(SubjectType.PAIRWISE);
registerRequest.setSectorIdentifierUri(sectorIdentifierUri);
registerRequest.setAccessTokenLifetime(3);

RegisterClient registerClient = new RegisterClient(registrationEndpoint);
registerClient.setRequest(registerRequest);
RegisterResponse registerResponse = registerClient.exec();

showClient(registerClient);
assertEquals(registerResponse.getStatus(), 200, "Unexpected response code: " + registerResponse.getEntity());
assertNotNull(registerResponse.getClientId());
assertNotNull(registerResponse.getClientSecret());
assertNotNull(registerResponse.getRegistrationAccessToken());
assertNotNull(registerResponse.getClientIdIssuedAt());
assertNotNull(registerResponse.getClientSecretExpiresAt());

String clientId = registerResponse.getClientId();
String clientSecret = registerResponse.getClientSecret();

// 2. Request authorization and receive the authorization code.
String nonce = UUID.randomUUID().toString();
String state = UUID.randomUUID().toString();

AuthorizationRequest authorizationRequest = new AuthorizationRequest(responseTypes, clientId, scopes, redirectUri, nonce);
authorizationRequest.setState(state);

AuthorizationResponse authorizationResponse = authenticateResourceOwnerAndGrantAccess(
authorizationEndpoint, authorizationRequest, userId, userSecret);

assertNotNull(authorizationResponse.getLocation(), "The location is null");
assertNotNull(authorizationResponse.getCode(), "The authorization code is null");
assertNotNull(authorizationResponse.getState(), "The state is null");
assertNotNull(authorizationResponse.getScope(), "The scope is null");

String scope = authorizationResponse.getScope();
String authorizationCode = authorizationResponse.getCode();
String idToken = authorizationResponse.getIdToken();

// 3. Request access token using the authorization code.
TokenRequest tokenRequest = new TokenRequest(GrantType.AUTHORIZATION_CODE);
tokenRequest.setCode(authorizationCode);
tokenRequest.setRedirectUri(redirectUri);
tokenRequest.setAuthUsername(clientId);
tokenRequest.setAuthPassword(clientSecret);
tokenRequest.setAuthenticationMethod(AuthenticationMethod.CLIENT_SECRET_BASIC);

TokenClient tokenClient = new TokenClient(tokenEndpoint);
tokenClient.setRequest(tokenRequest);
TokenResponse tokenResponse = tokenClient.exec();

showClient(tokenClient);
assertEquals(tokenResponse.getStatus(), 200, "Unexpected response code: " + tokenResponse.getStatus());
assertNotNull(tokenResponse.getEntity(), "The entity is null");
assertNotNull(tokenResponse.getAccessToken(), "The access token is null");
assertNotNull(tokenResponse.getExpiresIn(), "The expires in value is null");
assertNotNull(tokenResponse.getTokenType(), "The token type is null");
assertNotNull(tokenResponse.getRefreshToken(), "The refresh token is null");

String accessToken = tokenResponse.getAccessToken();

// 4. Request user info
{
UserInfoClient userInfoClient = new UserInfoClient(userInfoEndpoint);
UserInfoResponse userInfoResponse = userInfoClient.execUserInfo(accessToken);

showClient(userInfoClient);
assertEquals(userInfoResponse.getStatus(), 200, "Unexpected response code: " + userInfoResponse.getStatus());
assertNotNull(userInfoResponse.getClaim(JwtClaimName.SUBJECT_IDENTIFIER));
assertNotNull(userInfoResponse.getClaim(JwtClaimName.NAME));
assertNotNull(userInfoResponse.getClaim(JwtClaimName.BIRTHDATE));
assertNotNull(userInfoResponse.getClaim(JwtClaimName.FAMILY_NAME));
assertNotNull(userInfoResponse.getClaim(JwtClaimName.GENDER));
assertNotNull(userInfoResponse.getClaim(JwtClaimName.GIVEN_NAME));
assertNotNull(userInfoResponse.getClaim(JwtClaimName.MIDDLE_NAME));
assertNotNull(userInfoResponse.getClaim(JwtClaimName.NICKNAME));
assertNotNull(userInfoResponse.getClaim(JwtClaimName.PICTURE));
assertNotNull(userInfoResponse.getClaim(JwtClaimName.PREFERRED_USERNAME));
assertNotNull(userInfoResponse.getClaim(JwtClaimName.PROFILE));
assertNotNull(userInfoResponse.getClaim(JwtClaimName.WEBSITE));
assertNotNull(userInfoResponse.getClaim(JwtClaimName.EMAIL));
assertNotNull(userInfoResponse.getClaim(JwtClaimName.EMAIL_VERIFIED));
assertNotNull(userInfoResponse.getClaim(JwtClaimName.PHONE_NUMBER));
assertNotNull(userInfoResponse.getClaim(JwtClaimName.PHONE_NUMBER_VERIFIED));
assertNotNull(userInfoResponse.getClaim(JwtClaimName.ADDRESS));
assertNotNull(userInfoResponse.getClaim(JwtClaimName.LOCALE));
assertNotNull(userInfoResponse.getClaim(JwtClaimName.ZONEINFO));
assertNotNull(userInfoResponse.getClaim(JwtClaimName.USER_NAME));
assertNull(userInfoResponse.getClaim("org_name"));
assertNull(userInfoResponse.getClaim("work_phone"));
}

Thread.sleep(5000);

// 5. Request user info
{
UserInfoClient userInfoClient = new UserInfoClient(userInfoEndpoint);
UserInfoResponse userInfoResponse = userInfoClient.execUserInfo(accessToken);

showClient(userInfoClient);
assertEquals(userInfoResponse.getStatus(), 400, "Unexpected response code: " + userInfoResponse.getStatus());
assertNotNull(userInfoResponse.getErrorType(), "Unexpected result: errorType not found");
assertNotNull(userInfoResponse.getErrorDescription(), "Unexpected result: errorDescription not found");
}
}
}
7 changes: 7 additions & 0 deletions Client/src/test/resources/testng.xml
Expand Up @@ -86,6 +86,13 @@
</classes>
</test>

<!-- Client Specific Access Token Expiration -->
<test name="Client Specific Access Token Expiration" enabled="true">
<classes>
<class name="org.xdi.oxauth.ws.rs.ClientSpecificAccessTokenExpiration"/>
</classes>
</test>

<!-- Client White List Black List Redirect Uris -->
<test name="Client White List Black List Redirect Uris" enabled="true">
<classes>
Expand Down
Expand Up @@ -13,7 +13,7 @@
*
* @author Yuriy Zabrovarnyy
* @author Javier Rojas Blum
* @version June 20, 2018
* @version November 28, 2018
*/

public enum RegisterRequestParam {
Expand Down Expand Up @@ -259,7 +259,12 @@ public enum RegisterRequestParam {
/**
* Authorized JavaScript origins.
*/
AUTHORIZED_ORIGINS("authorized_origins");
AUTHORIZED_ORIGINS("authorized_origins"),

/**
* Client-specific access token expiration. Set this value to null or zero to use the default value.
*/
ACCESS_TOKEN_LIFETIME("access_token_lifetime");

/**
* Parameter name
Expand Down
Expand Up @@ -6,18 +6,6 @@

package org.xdi.oxauth.model.common;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArraySet;

import javax.inject.Inject;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xdi.oxauth.model.authorize.JwtAuthorizationRequest;
Expand All @@ -27,11 +15,17 @@
import org.xdi.oxauth.model.registration.Client;
import org.xdi.oxauth.util.TokenHashUtil;

import javax.inject.Inject;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArraySet;

/**
* @author Yuriy Zabrovarnyy
* @author Javier Rojas Blum
* @author Yuriy Movchan
* @version September 6, 2017
* @version November 28, 2018
*/

public abstract class AbstractAuthorizationGrant implements IAuthorizationGrant {
Expand Down Expand Up @@ -278,6 +272,10 @@ public String checkScopesPolicy(String requestedScopes) {
@Override
public AccessToken createAccessToken() {
int lifetime = appConfiguration.getAccessTokenLifetime();
// oxAuth #830 Client-specific access token expiration
if (client != null && client.getAccessTokenLifetime() != null && client.getAccessTokenLifetime() > 0) {
lifetime = client.getAccessTokenLifetime();
}
AccessToken accessToken = new AccessToken(lifetime);

accessToken.setAuthMode(getAcrValues());
Expand Down
25 changes: 17 additions & 8 deletions Server/src/main/java/org/xdi/oxauth/model/common/CacheGrant.java
@@ -1,18 +1,23 @@
package org.xdi.oxauth.model.common;

import java.io.Serializable;
import java.util.Date;
import java.util.Set;
/*
* oxAuth is available under the MIT License (2008). See http://opensource.org/licenses/MIT for full text.
*
* Copyright (c) 2014, Gluu
*/

import javax.enterprise.inject.Instance;
package org.xdi.oxauth.model.common;

import org.apache.commons.lang.StringUtils;
import org.xdi.oxauth.model.configuration.AppConfiguration;
import org.xdi.oxauth.model.registration.Client;

import javax.enterprise.inject.Instance;
import java.io.Serializable;
import java.util.Date;
import java.util.Set;

/**
* @author yuriyz
* @version September 6, 2017
* @version November 28, 2018
*/
public class CacheGrant implements Serializable {

Expand Down Expand Up @@ -65,6 +70,10 @@ private void initExpiresIn(AuthorizationGrant grant, AppConfiguration appConfigu
expiresIn = grant.getAuthorizationCode().getExpiresIn();
} else {
expiresIn = appConfiguration.getAccessTokenLifetime();
// oxAuth #830 Client-specific access token expiration
if (client != null && client.getAccessTokenLifetime() != null && client.getAccessTokenLifetime() > 0) {
expiresIn = client.getAccessTokenLifetime();
}
}
}

Expand Down Expand Up @@ -193,7 +202,7 @@ public String cacheKey() {
return cacheKey(client.getClientId(), authorizationCodeString, grantId);
}

public static String cacheKey(String clientId, String code, String grantId ) {
public static String cacheKey(String clientId, String code, String grantId) {
if (StringUtils.isBlank(code)) {
return grantId;
}
Expand Down

0 comments on commit 1caf702

Please sign in to comment.