Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(jans-auth-server): set public subject identifier per client #800

Merged
Merged
Show file tree
Hide file tree
Changes from 2 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 @@ -11,11 +11,7 @@
import com.google.common.collect.Sets;
import io.jans.as.client.model.SoftwareStatement;
import io.jans.as.client.util.ClientUtil;
import io.jans.as.model.common.AuthenticationMethod;
import io.jans.as.model.common.BackchannelTokenDeliveryMode;
import io.jans.as.model.common.GrantType;
import io.jans.as.model.common.ResponseType;
import io.jans.as.model.common.SubjectType;
import io.jans.as.model.common.*;
import io.jans.as.model.crypto.AuthCryptoProvider;
import io.jans.as.model.crypto.encryption.BlockEncryptionAlgorithm;
import io.jans.as.model.crypto.encryption.KeyEncryptionAlgorithm;
Expand All @@ -31,78 +27,10 @@
import org.json.JSONObject;

import javax.ws.rs.core.MediaType;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import static io.jans.as.client.util.ClientUtil.booleanOrNull;
import static io.jans.as.client.util.ClientUtil.extractListByKey;
import static io.jans.as.client.util.ClientUtil.integerOrNull;
import static io.jans.as.model.register.RegisterRequestParam.ACCESS_TOKEN_AS_JWT;
import static io.jans.as.model.register.RegisterRequestParam.ACCESS_TOKEN_LIFETIME;
import static io.jans.as.model.register.RegisterRequestParam.ACCESS_TOKEN_SIGNING_ALG;
import static io.jans.as.model.register.RegisterRequestParam.ALLOW_SPONTANEOUS_SCOPES;
import static io.jans.as.model.register.RegisterRequestParam.APPLICATION_TYPE;
import static io.jans.as.model.register.RegisterRequestParam.AUTHORIZATION_ENCRYPTED_RESPONSE_ALG;
import static io.jans.as.model.register.RegisterRequestParam.AUTHORIZATION_ENCRYPTED_RESPONSE_ENC;
import static io.jans.as.model.register.RegisterRequestParam.AUTHORIZATION_SIGNED_RESPONSE_ALG;
import static io.jans.as.model.register.RegisterRequestParam.AUTHORIZED_ORIGINS;
import static io.jans.as.model.register.RegisterRequestParam.BACKCHANNEL_AUTHENTICATION_REQUEST_SIGNING_ALG;
import static io.jans.as.model.register.RegisterRequestParam.BACKCHANNEL_CLIENT_NOTIFICATION_ENDPOINT;
import static io.jans.as.model.register.RegisterRequestParam.BACKCHANNEL_LOGOUT_SESSION_REQUIRED;
import static io.jans.as.model.register.RegisterRequestParam.BACKCHANNEL_LOGOUT_URI;
import static io.jans.as.model.register.RegisterRequestParam.BACKCHANNEL_TOKEN_DELIVERY_MODE;
import static io.jans.as.model.register.RegisterRequestParam.BACKCHANNEL_USER_CODE_PARAMETER;
import static io.jans.as.model.register.RegisterRequestParam.CLAIMS;
import static io.jans.as.model.register.RegisterRequestParam.CLAIMS_REDIRECT_URIS;
import static io.jans.as.model.register.RegisterRequestParam.CLIENT_NAME;
import static io.jans.as.model.register.RegisterRequestParam.CLIENT_URI;
import static io.jans.as.model.register.RegisterRequestParam.CONTACTS;
import static io.jans.as.model.register.RegisterRequestParam.DEFAULT_ACR_VALUES;
import static io.jans.as.model.register.RegisterRequestParam.DEFAULT_MAX_AGE;
import static io.jans.as.model.register.RegisterRequestParam.FRONT_CHANNEL_LOGOUT_SESSION_REQUIRED;
import static io.jans.as.model.register.RegisterRequestParam.FRONT_CHANNEL_LOGOUT_URI;
import static io.jans.as.model.register.RegisterRequestParam.GRANT_TYPES;
import static io.jans.as.model.register.RegisterRequestParam.ID_TOKEN_ENCRYPTED_RESPONSE_ALG;
import static io.jans.as.model.register.RegisterRequestParam.ID_TOKEN_ENCRYPTED_RESPONSE_ENC;
import static io.jans.as.model.register.RegisterRequestParam.ID_TOKEN_SIGNED_RESPONSE_ALG;
import static io.jans.as.model.register.RegisterRequestParam.ID_TOKEN_TOKEN_BINDING_CNF;
import static io.jans.as.model.register.RegisterRequestParam.INITIATE_LOGIN_URI;
import static io.jans.as.model.register.RegisterRequestParam.JWKS;
import static io.jans.as.model.register.RegisterRequestParam.JWKS_URI;
import static io.jans.as.model.register.RegisterRequestParam.KEEP_CLIENT_AUTHORIZATION_AFTER_EXPIRATION;
import static io.jans.as.model.register.RegisterRequestParam.LOGO_URI;
import static io.jans.as.model.register.RegisterRequestParam.PAR_LIFETIME;
import static io.jans.as.model.register.RegisterRequestParam.POLICY_URI;
import static io.jans.as.model.register.RegisterRequestParam.POST_LOGOUT_REDIRECT_URIS;
import static io.jans.as.model.register.RegisterRequestParam.REDIRECT_URIS;
import static io.jans.as.model.register.RegisterRequestParam.REQUEST_OBJECT_ENCRYPTION_ALG;
import static io.jans.as.model.register.RegisterRequestParam.REQUEST_OBJECT_ENCRYPTION_ENC;
import static io.jans.as.model.register.RegisterRequestParam.REQUEST_OBJECT_SIGNING_ALG;
import static io.jans.as.model.register.RegisterRequestParam.REQUEST_URIS;
import static io.jans.as.model.register.RegisterRequestParam.REQUIRE_AUTH_TIME;
import static io.jans.as.model.register.RegisterRequestParam.REQUIRE_PAR;
import static io.jans.as.model.register.RegisterRequestParam.RESPONSE_TYPES;
import static io.jans.as.model.register.RegisterRequestParam.RPT_AS_JWT;
import static io.jans.as.model.register.RegisterRequestParam.RUN_INTROSPECTION_SCRIPT_BEFORE_ACCESS_TOKEN_CREATION_AS_JWT_AND_INCLUDE_CLAIMS;
import static io.jans.as.model.register.RegisterRequestParam.SCOPE;
import static io.jans.as.model.register.RegisterRequestParam.SECTOR_IDENTIFIER_URI;
import static io.jans.as.model.register.RegisterRequestParam.SOFTWARE_ID;
import static io.jans.as.model.register.RegisterRequestParam.SOFTWARE_STATEMENT;
import static io.jans.as.model.register.RegisterRequestParam.SOFTWARE_VERSION;
import static io.jans.as.model.register.RegisterRequestParam.SPONTANEOUS_SCOPES;
import static io.jans.as.model.register.RegisterRequestParam.SUBJECT_TYPE;
import static io.jans.as.model.register.RegisterRequestParam.TLS_CLIENT_AUTH_SUBJECT_DN;
import static io.jans.as.model.register.RegisterRequestParam.TOKEN_ENDPOINT_AUTH_METHOD;
import static io.jans.as.model.register.RegisterRequestParam.TOKEN_ENDPOINT_AUTH_SIGNING_ALG;
import static io.jans.as.model.register.RegisterRequestParam.TOS_URI;
import static io.jans.as.model.register.RegisterRequestParam.USERINFO_ENCRYPTED_RESPONSE_ALG;
import static io.jans.as.model.register.RegisterRequestParam.USERINFO_ENCRYPTED_RESPONSE_ENC;
import static io.jans.as.model.register.RegisterRequestParam.USERINFO_SIGNED_RESPONSE_ALG;
import java.util.*;

import static io.jans.as.client.util.ClientUtil.*;
import static io.jans.as.model.register.RegisterRequestParam.*;
import static io.jans.as.model.util.StringUtils.implode;
import static io.jans.as.model.util.StringUtils.toJSONArray;

Expand All @@ -111,7 +39,7 @@
*
* @author Javier Rojas Blum
* @author Yuriy Zabrovarnyy
* @version July 28, 2021
* @version February 10, 2022
*/
public class RegisterRequest extends BaseRequest {

Expand Down Expand Up @@ -154,6 +82,7 @@ public class RegisterRequest extends BaseRequest {
private Boolean runIntrospectionScriptBeforeAccessTokenAsJwtCreationAndIncludeClaims;
private Boolean keepClientAuthorizationAfterExpiration;
private SubjectType subjectType;
private String subjectIdentifierAttribute;
private Boolean rptAsJwt;
private Boolean accessTokenAsJwt;
private SignatureAlgorithm accessTokenSigningAlg;
Expand Down Expand Up @@ -697,6 +626,14 @@ public void setSubjectType(SubjectType subjectType) {
this.subjectType = subjectType;
}

public String getSubjectIdentifierAttribute() {
return subjectIdentifierAttribute;
}

public void setSubjectIdentifierAttribute(String subjectIdentifierAttribute) {
this.subjectIdentifierAttribute = subjectIdentifierAttribute;
}

public Boolean getRptAsJwt() {
return rptAsJwt;
}
Expand Down Expand Up @@ -1305,6 +1242,9 @@ public Map<String, String> getParameters() {
if (subjectType != null) {
parameters.put(SUBJECT_TYPE.toString(), subjectType.toString());
}
if (StringUtils.isNotBlank(subjectIdentifierAttribute)) {
parameters.put(PUBLIC_SUBJECT_IDENTIFIER_ATTRIBUTE.getName(), subjectIdentifierAttribute);
}
if (rptAsJwt != null) {
parameters.put(RPT_AS_JWT.toString(), rptAsJwt.toString());
}
Expand Down Expand Up @@ -1521,6 +1461,7 @@ public static RegisterRequest fromJson(JSONObject requestObject) throws JSONExce
result.setJwks(requestObject.optString(JWKS.toString()));
result.setSectorIdentifierUri(requestObject.optString(SECTOR_IDENTIFIER_URI.toString()));
result.setSubjectType(SubjectType.fromString(requestObject.optString(SUBJECT_TYPE.toString())));
result.setSubjectIdentifierAttribute(requestObject.optString(PUBLIC_SUBJECT_IDENTIFIER_ATTRIBUTE.getName()));
result.setSoftwareId(requestObject.optString(SOFTWARE_ID.toString()));
result.setSoftwareVersion(requestObject.optString(SOFTWARE_VERSION.toString()));
result.setSoftwareStatement(requestObject.optString(SOFTWARE_STATEMENT.toString()));
Expand Down Expand Up @@ -1616,6 +1557,9 @@ public JSONObject getJSONParameters() throws JSONException {
if (subjectType != null) {
parameters.put(SUBJECT_TYPE.toString(), subjectType.toString());
}
if (StringUtils.isNotBlank(subjectIdentifierAttribute)) {
parameters.put(PUBLIC_SUBJECT_IDENTIFIER_ATTRIBUTE.getName(), subjectIdentifierAttribute);
}
if (rptAsJwt != null) {
parameters.put(RPT_AS_JWT.toString(), rptAsJwt.toString());
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
/*
* Janssen Project software is available under the Apache License (2004). See http://www.apache.org/licenses/ for full text.
*
* Copyright (c) 2020, Janssen Project
*/

package io.jans.as.client.ws.rs;

import io.jans.as.client.*;
import io.jans.as.model.common.ResponseType;
import io.jans.as.model.common.SubjectType;
import io.jans.as.model.crypto.signature.RSAPublicKey;
import io.jans.as.model.crypto.signature.SignatureAlgorithm;
import io.jans.as.model.exception.InvalidJwtException;
import io.jans.as.model.jws.RSASigner;
import io.jans.as.model.jwt.Jwt;
import io.jans.as.model.jwt.JwtClaimName;
import io.jans.as.model.jwt.JwtHeaderName;
import io.jans.as.model.register.ApplicationType;
import io.jans.as.model.util.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.testng.ITestContext;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Parameters;
import org.testng.annotations.Test;

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

import static org.testng.Assert.*;

/**
* @author Javier Rojas Blum
* @version February 10, 2022
*/
public class SetPublicSubjectIdentifierPerClient extends BaseTest {
qbert2k marked this conversation as resolved.
Show resolved Hide resolved

@Test(dataProvider = "dataMethod")
public void setPublicSubjectIdentifierPerClient(
final String redirectUri, final String userId, final String userSecret,
final String subjectIdentifierAttribute, final String expectedSubValue) throws InvalidJwtException {
showTitle("setPublicSubjectIdentifierPerClient");

List<ResponseType> responseTypes = Arrays.asList(
ResponseType.TOKEN,
ResponseType.ID_TOKEN);

RegisterResponse registerResponse = clientRegistration(redirectUri, responseTypes,
SubjectType.PUBLIC, subjectIdentifierAttribute, false);

String clientId = registerResponse.getClientId();

List<String> scopes = Arrays.asList("openid", "profile", "address", "email");
String state = UUID.randomUUID().toString();
String nonce = 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());
assertNotNull(authorizationResponse.getAccessToken());
assertNotNull(authorizationResponse.getState());
assertNotNull(authorizationResponse.getScope());
qbert2k marked this conversation as resolved.
Show resolved Hide resolved

String idToken = authorizationResponse.getIdToken();

Jwt jwt = Jwt.parse(idToken);
RSAPublicKey publicKey = JwkClient.getRSAPublicKey(
jwksUri,
jwt.getHeader().getClaimAsString(JwtHeaderName.KEY_ID));
RSASigner rsaSigner = new RSASigner(SignatureAlgorithm.RS256, publicKey);
assertTrue(rsaSigner.validate(jwt));

assertEquals(jwt.getClaims().getClaimAsString(JwtClaimName.SUBJECT_IDENTIFIER), expectedSubValue);
}

@Parameters({"redirectUri"})
@Test
public void setPublicSubjectIdentifierPerClientFail1(final String redirectUri) {
showTitle("setPublicSubjectIdentifierPerClientFail1");

List<ResponseType> responseTypes = Arrays.asList(
ResponseType.TOKEN,
ResponseType.ID_TOKEN);

clientRegistration(redirectUri, responseTypes, SubjectType.PAIRWISE, "mail", true);
}

@Parameters({"redirectUri"})
@Test
public void setPublicSubjectIdentifierPerClientFail2(final String redirectUri) {
showTitle("setPublicSubjectIdentifierPerClientFail2");

List<ResponseType> responseTypes = Arrays.asList(
ResponseType.TOKEN,
ResponseType.ID_TOKEN);

clientRegistration(redirectUri, responseTypes, SubjectType.PUBLIC, "invalid_attribute", true);
}

@NotNull
private RegisterResponse clientRegistration(
String redirectUri, List<ResponseType> responseTypes, SubjectType subjectType,
String subjectIdentifierAttribute, boolean checkError) {
List<String> redirectUriList = Arrays.asList(redirectUri.split(StringUtils.SPACE));
RegisterRequest registerRequest = new RegisterRequest(ApplicationType.WEB, "jans test app", redirectUriList);
registerRequest.setSubjectType(subjectType);
registerRequest.setResponseTypes(responseTypes);
registerRequest.setSubjectIdentifierAttribute(subjectIdentifierAttribute);

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

showClient(registerClient);

if (checkError) {
assertEquals(registerResponse.getStatus(), 400);
assertNotNull(registerResponse.getEntity());
assertNotNull(registerResponse.getErrorType());
assertNotNull(registerResponse.getErrorDescription());
} else {
assertEquals(registerResponse.getStatus(), 201, "Unexpected response code: " + registerResponse.getEntity());
assertNotNull(registerResponse.getClientId());
assertNotNull(registerResponse.getClientSecret());
assertNotNull(registerResponse.getRegistrationAccessToken());
assertNotNull(registerResponse.getClientSecretExpiresAt());
qbert2k marked this conversation as resolved.
Show resolved Hide resolved
}

return registerResponse;
}

@DataProvider(name = "dataMethod")
public Object[] dataMethod(ITestContext context) {
final String redirectUri = context.getCurrentXmlTest().getParameter("redirectUri");
final String userId = context.getCurrentXmlTest().getParameter("userId");
final String userSecret = context.getCurrentXmlTest().getParameter("userSecret");

return new Object[][]{
{redirectUri, userId, userSecret, "mail", "test_user@test.org"},
{redirectUri, userId, userSecret, "uid", "test_user"}
};
}
}
10 changes: 8 additions & 2 deletions jans-auth-server/client/src/test/resources/testng.xml
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@

<test name="Authorization Support Custom Params" enabled="true">
<classes>
<class name="io.jans.as.client.ws.rs.AuthorizationSupportCustomParams"></class>
<class name="io.jans.as.client.ws.rs.AuthorizationSupportCustomParams"/>
</classes>
</test>

Expand Down Expand Up @@ -238,6 +238,12 @@
</classes>
</test>

<test name="set Public Subject Identifier per Client" enabled="true">
<classes>
<class name="io.jans.as.client.ws.rs.SetPublicSubjectIdentifierPerClient"/>
</classes>
</test>

<!-- SSO with Multiple Backend Services test -->
<test name="SSO with Multiple Backend Services test (HTTP)" enabled="true">
<classes>
Expand Down Expand Up @@ -282,7 +288,7 @@

<test name="Turn off consent for pairwise / openid-only scope" enabled="true">
<classes>
<class name="io.jans.as.client.ws.rs.TurnOffConsentForPairwiseOpenIdOnlyConsentTest"></class>
<class name="io.jans.as.client.ws.rs.TurnOffConsentForPairwiseOpenIdOnlyConsentTest"/>
</classes>
</test>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,7 @@

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.google.common.collect.Lists;
import io.jans.as.model.common.ComponentType;
import io.jans.as.model.common.GrantType;
import io.jans.as.model.common.ResponseMode;
import io.jans.as.model.common.ResponseType;
import io.jans.as.model.common.SoftwareStatementValidationType;
import io.jans.as.model.common.WebKeyStorage;
import io.jans.as.model.common.*;
import io.jans.as.model.error.ErrorHandlingMethod;
import io.jans.as.model.jwk.KeySelectionStrategy;

Expand All @@ -28,7 +23,7 @@
* @author Javier Rojas Blum
* @author Yuriy Zabrovarnyy
* @author Yuriy Movchan
* @version February 2, 2022
* @version February 10, 2022
*/
@JsonIgnoreProperties(ignoreUnknown = true)
public class AppConfiguration implements Configuration {
Expand Down Expand Up @@ -94,6 +89,8 @@ public class AppConfiguration implements Configuration {

private int spontaneousScopeLifetime;
private String openidSubAttribute;
private Boolean publicSubjectIdentifierPerClientEnabled = false;
private List<String> subjectIdentifiersPerClientSupported;
private Set<Set<ResponseType>> responseTypesSupported;
private Set<ResponseMode> responseModesSupported;
private Set<GrantType> grantTypesSupported;
Expand Down Expand Up @@ -1028,6 +1025,30 @@ public void setOpenidSubAttribute(String openidSubAttribute) {
this.openidSubAttribute = openidSubAttribute;
}

public Boolean getPublicSubjectIdentifierPerClientEnabled() {
if (publicSubjectIdentifierPerClientEnabled == null) {
publicSubjectIdentifierPerClientEnabled = false;
}

return publicSubjectIdentifierPerClientEnabled;
}

public void setPublicSubjectIdentifierPerClientEnabled(Boolean publicSubjectIdentifierPerClientEnabled) {
this.publicSubjectIdentifierPerClientEnabled = publicSubjectIdentifierPerClientEnabled;
}

public List<String> getSubjectIdentifiersPerClientSupported() {
if (subjectIdentifiersPerClientSupported == null) {
subjectIdentifiersPerClientSupported = new ArrayList<>();
}

return subjectIdentifiersPerClientSupported;
}

public void setSubjectIdentifiersPerClientSupported(List<String> subjectIdentifiersPerClientSupported) {
this.subjectIdentifiersPerClientSupported = subjectIdentifiersPerClientSupported;
}

public String getIdGenerationEndpoint() {
return idGenerationEndpoint;
}
Expand Down
Loading