Skip to content

Commit

Permalink
feat: support regex client property to validate redirect uris
Browse files Browse the repository at this point in the history
  • Loading branch information
Milton-Ch committed Mar 10, 2022
1 parent 3f58aff commit 6cfa087
Show file tree
Hide file tree
Showing 10 changed files with 312 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ public class RegisterRequest extends BaseRequest {
private AsymmetricSignatureAlgorithm backchannelAuthenticationRequestSigningAlg;
private Boolean backchannelUserCodeParameter;
private List<String> additionalAudience;
private String redirectUrisRegex;

/**
* String containing a space-separated list of scope values. (correct name is 'scope' not 'scopes', see (rfc7591).)
Expand Down Expand Up @@ -1396,6 +1397,10 @@ public Map<String, String> getParameters() {
parameters.put(DEFAULT_PROMPT_LOGIN.getName(), defaultPromptLogin.toString());
}

if (redirectUrisRegex != null) {
parameters.put(REDIRECT_URIS_REGEX.toString(), redirectUrisRegex.toString()) ;
}

// Custom params
if (customAttributes != null && !customAttributes.isEmpty()) {
for (Map.Entry<String, String> entry : customAttributes.entrySet()) {
Expand Down Expand Up @@ -1481,6 +1486,7 @@ public static RegisterRequest fromJson(JSONObject requestObject) throws JSONExce
result.setBackchannelClientNotificationEndpoint(requestObject.optString(BACKCHANNEL_CLIENT_NOTIFICATION_ENDPOINT.toString()));
result.setBackchannelAuthenticationRequestSigningAlg(AsymmetricSignatureAlgorithm.fromString(requestObject.optString(BACKCHANNEL_AUTHENTICATION_REQUEST_SIGNING_ALG.toString())));
result.setBackchannelUserCodeParameter(booleanOrNull(requestObject, BACKCHANNEL_USER_CODE_PARAMETER.toString()));
result.setRedirectUrisRegex(requestObject.optString(REDIRECT_URIS_REGEX.toString()));
result.setDefaultPromptLogin(requestObject.optBoolean(DEFAULT_PROMPT_LOGIN.getName()));

return result;
Expand Down Expand Up @@ -1693,9 +1699,15 @@ public JSONObject getJSONParameters() throws JSONException {
if (backchannelUserCodeParameter != null) {
parameters.put(BACKCHANNEL_USER_CODE_PARAMETER.toString(), backchannelUserCodeParameter);
}

if (redirectUrisRegex != null) {
parameters.put(REDIRECT_URIS_REGEX.toString(), redirectUrisRegex) ;
}

if (defaultPromptLogin != null) {
parameters.put(DEFAULT_PROMPT_LOGIN.getName(), defaultPromptLogin);
}

// Custom params
if (customAttributes != null && !customAttributes.isEmpty()) {
for (Map.Entry<String, String> entry : customAttributes.entrySet()) {
Expand Down Expand Up @@ -1754,4 +1766,12 @@ public void setJwtRequestAsString(String jwtRequestAsString) {
public boolean hasJwtRequestAsString() {
return StringUtils.isNotBlank(jwtRequestAsString);
}
}

public String getRedirectUrisRegex() {
return redirectUrisRegex;
}

public void setRedirectUrisRegex(String redirectUrisRegex) {
this.redirectUrisRegex = redirectUrisRegex;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package io.jans.as.client.ws.rs;

import io.jans.as.client.*;
import io.jans.as.model.common.ResponseType;
import io.jans.as.model.register.ApplicationType;
import io.jans.as.model.util.StringUtils;
import org.testng.annotations.Parameters;
import org.testng.annotations.Test;

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

import static io.jans.as.client.client.Asserter.*;
import static io.jans.as.model.register.RegisterRequestParam.*;

/**
* Integration tests to validate redirect uris regex behavior
*
*/
public class AuthorizationRedirectUrisRegexTest extends BaseTest {

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

List<ResponseType> responseTypes = Arrays.asList(ResponseType.CODE);

// 1. Register client
RegisterRequest registerRequest = new RegisterRequest(ApplicationType.WEB, "jans test app",
StringUtils.spaceSeparatedToList(redirectUris));
registerRequest.setResponseTypes(responseTypes);
registerRequest.setSectorIdentifierUri(sectorIdentifierUri);
registerRequest.setRedirectUrisRegex(redirectUrisRegex);
registerRequest.setRedirectUris(getRedirectUris());

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

showClient(registerClient);
assertRegisterResponseOk(registerResponse, 201, true);

String clientId = registerResponse.getClientId();
String registrationAccessToken = registerResponse.getRegistrationAccessToken();
String registrationClientUri = registerResponse.getRegistrationClientUri();

// 2. Client read
RegisterRequest readClientRequest = new RegisterRequest(registrationAccessToken);

RegisterClient readClient = new RegisterClient(registrationClientUri);
readClient.setRequest(readClientRequest);
RegisterResponse readClientResponse = readClient.exec();

showClient(readClient);
assertRegisterResponseOk(readClientResponse, 200, false);

assertRegisterResponseClaimsNotNull(readClientResponse, RESPONSE_TYPES, REDIRECT_URIS. APPLICATION_TYPE, CLIENT_NAME, ID_TOKEN_SIGNED_RESPONSE_ALG, SCOPE);

// 3. Request authorization and receive the authorization code.
List<String> scopes = Arrays.asList("openid", "profile", "address", "email");
String state = UUID.randomUUID().toString();

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

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

assertAuthorizationResponse(authorizationResponse, true);

}

private List<String> getRedirectUris() {
return Arrays.asList("https://www.jans.org",
"http://localhost:80/jans-auth-rp/home.htm",
"https://localhost:8443/jans-auth-rp/home.htm",
"https://client.example.org/callback",
"https://client.example.org/callback2",
"https://client.other_company.example.net/callback",
"https://client.example.com/cb",
"https://client.example.com/cb1",
"https://client.example.com/cb2") ;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ clientSecret=${auth.client.secret}
audience=https://${test.server.name}
redirectUri=https://${test.server.name}/jans-auth-rp/home.htm
redirectUris=https://${test.server.name}/jans-auth-rp/home.htm https://client.example.com/cb https://client.example.com/cb1 https://client.example.com/cb2
redirectUrisRegex=/^([a-z0-9+.-]+):(?://(?:((?:[a-z0-9-._~!$&'()*+,;=:]|%[0-9A-F]{2})*)@)?((?:[a-z0-9-._~!$&'()*+,;=]|%[0-9A-F]{2})*)(?::(\\d*))?(/(?:[a-z0-9-._~!$&'()*+,;=:@/]|%[0-9A-F]{2})*)?|(/?(?:[a-z0-9-._~!$&'()*+,;=:@]|%[0-9A-F]{2})+(?:[a-z0-9-._~!$&'()*+,;=:@/]|%[0-9A-F]{2})*)?)(?:\\?((?:[a-z0-9-._~!$&'()*+,;=:/?@]|%[0-9A-F]{2})*))?(?:#((?:[a-z0-9-._~!$&'()*+,;=:/?@]|%[0-9A-F]{2})*))?$/i
#redirectUris=https://${test.server.name}/jans-auth-rp/home.htm https://client.example.com/cb https://client.example.com/cb1 https://client.example.com/cb2 https://openid.implicit.client.test/login-callback.html
logoutUri=https://${test.server.name}/jans-auth-rp/home.htm
postLogoutRedirectUri=https://client.example.com/pl
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,9 @@ public class AppConfiguration implements Configuration {
private int expirationNotificatorMapSizeLimit = 100000;
private int expirationNotificatorIntervalInSeconds = 600;

//feature flags
private Boolean redirectUrisRegexEnabled = false;

private Boolean authenticationFiltersEnabled;
private Boolean clientAuthenticationFiltersEnabled;
private Boolean clientRegDefaultToCodeFlowWithRefresh;
Expand Down Expand Up @@ -2459,4 +2462,12 @@ public int getDpopJtiCacheTime() {
public void setDpopJtiCacheTime(int dpopJtiCacheTime) {
this.dpopJtiCacheTime = dpopJtiCacheTime;
}

public Boolean getRedirectUrisRegexEnabled() {
return redirectUrisRegexEnabled != null && redirectUrisRegexEnabled;
}

public void setRedirectUrisRegexEnabled(Boolean redirectUrisRegexEnabled) {
this.redirectUrisRegexEnabled = redirectUrisRegexEnabled;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,9 @@ public enum RegisterErrorResponseType implements IErrorType {
*/
ACCESS_DENIED("access_denied"),

INVALID_PUBLIC_SUBJECT_IDENTIFIER_ATTRIBUTE("invalid_public_subject_identifier_attribute");
INVALID_PUBLIC_SUBJECT_IDENTIFIER_ATTRIBUTE("invalid_public_subject_identifier_attribute"),

INVALID_REDIRECT_URIS_REGEX("invalid_redirect_uris_regex");

private final String paramName;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -347,9 +347,11 @@ public enum RegisterRequestParam {

BACKCHANNEL_USER_CODE_PARAMETER("backchannel_user_code_parameter"),

DEFAULT_PROMPT_LOGIN("default_prompt_login"),
PUBLIC_SUBJECT_IDENTIFIER_ATTRIBUTE("public_subject_identifier_attribute"),

PUBLIC_SUBJECT_IDENTIFIER_ATTRIBUTE("public_subject_identifier_attribute");
REDIRECT_URIS_REGEX("redirect_uris_regex"),

DEFAULT_PROMPT_LOGIN("default_prompt_login");

/**
* Parameter name
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,9 @@ public class ClientAttributes implements Serializable {
@JsonProperty("jansSubAttr")
private String publicSubjectIdentifierAttribute;

@JsonProperty("redirectUrisRegex")
private String redirectUrisRegex ;

@JsonProperty("jansDefaultPromptLogin")
private Boolean defaultPromptLogin = false;

Expand Down Expand Up @@ -296,6 +299,14 @@ public void setPublicSubjectIdentifierAttribute(String publicSubjectIdentifierAt
this.publicSubjectIdentifierAttribute = publicSubjectIdentifierAttribute;
}

public String getRedirectUrisRegex() {
return redirectUrisRegex;
}

public void setRedirectUrisRegex(String redirectUrisRegex) {
this.redirectUrisRegex = redirectUrisRegex;
}

public Boolean getDefaultPromptLogin() {
if (defaultPromptLogin == null) {
defaultPromptLogin = false;
Expand Down Expand Up @@ -329,6 +340,7 @@ public String toString() {
", authorizationEncryptedResponseAlg=" + authorizationEncryptedResponseAlg +
", authorizationEncryptedResponseEnc=" + authorizationEncryptedResponseEnc +
", publicSubjectIdentifierAttribute=" + publicSubjectIdentifierAttribute +
", redirectUrisRegex=" + redirectUrisRegex +
", defaultPromptLogin=" + defaultPromptLogin +
'}';
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -310,8 +310,8 @@ private Response registerClientImpl(String requestParams, HttpServletRequest htt

builder.entity(jsonObjectToString(jsonObject));

log.info("Client registered: clientId = {}, applicationType = {}, clientName = {}, redirectUris = {}, sectorIdentifierUri = {}",
client.getClientId(), client.getApplicationType(), client.getClientName(), client.getRedirectUris(), client.getSectorIdentifierUri());
log.info("Client registered: clientId = {}, applicationType = {}, clientName = {}, redirectUris = {}, sectorIdentifierUri = {}, redirectUrisRegex = {}" ,
client.getClientId(), client.getApplicationType(), client.getClientName(), client.getRedirectUris(), client.getSectorIdentifierUri(), client.getAttributes().getRedirectUrisRegex());

oAuth2AuditLog.setClientId(client.getClientId());
oAuth2AuditLog.setScope(clientScopesToString(client));
Expand Down Expand Up @@ -364,6 +364,16 @@ private void validateSubjectIdentifierAttribute(RegisterRequest registerRequest)
);
}
}

if (StringUtils.isNotBlank(registerRequest.getRedirectUrisRegex())) {
if (Boolean.FALSE.equals(appConfiguration.getRedirectUrisRegexEnabled())) {
throw errorResponseFactory.createBadRequestException(
RegisterErrorResponseType.INVALID_REDIRECT_URIS_REGEX,
"The redirect URI's Regex is disabled."
);
}
}

}

private void setClientName(RegisterRequest r, Client client) {
Expand Down Expand Up @@ -676,6 +686,9 @@ private void updateClientFromRequestObject(Client client, RegisterRequest reques
if (requestObject.getTlsClientAuthSubjectDn() != null) {
client.getAttributes().setTlsClientAuthSubjectDn(requestObject.getTlsClientAuthSubjectDn());
}
if (requestObject.getRedirectUrisRegex() != null) {
client.getAttributes().setRedirectUrisRegex(requestObject.getRedirectUrisRegex());
}
if (requestObject.getAllowSpontaneousScopes() != null) {
client.getAttributes().setAllowSpontaneousScopes(requestObject.getAllowSpontaneousScopes());
}
Expand Down Expand Up @@ -1136,6 +1149,7 @@ private JSONObject getJSONObject(Client client) throws JSONException, StringEncr
Util.addToJSONObjectIfNotNull(responseJsonObject, FRONT_CHANNEL_LOGOUT_SESSION_REQUIRED.toString(), client.getFrontChannelLogoutSessionRequired());
Util.addToJSONObjectIfNotNull(responseJsonObject, BACKCHANNEL_LOGOUT_URI.toString(), client.getAttributes().getBackchannelLogoutUri());
Util.addToJSONObjectIfNotNull(responseJsonObject, BACKCHANNEL_LOGOUT_SESSION_REQUIRED.toString(), client.getAttributes().getBackchannelLogoutSessionRequired());
Util.addToJSONObjectIfNotNull(responseJsonObject, REDIRECT_URIS_REGEX.toString(), client.getAttributes().getRedirectUrisRegex());

// Custom Params
String[] scopeNames = null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,18 +106,19 @@ public String validateRedirectionUri(@NotNull Client client, String redirectionU
redirectUris = getSectorRedirectUris(sectorIdentifierUri).toArray(new String[0]);
}

if (StringUtils.isNotBlank(redirectionUri) && redirectUris != null) {
if (StringUtils.isBlank(sectorIdentifierUri) && redirectUris != null && redirectUris.length == 1) {
return redirectUris[0];
}

if (StringUtils.isNotBlank(redirectionUri)) {
log.debug("Validating redirection URI: clientIdentifier = {}, redirectionUri = {}, found = {}",
client.getClientId(), redirectionUri, redirectUris.length);

if (isUriEqual(redirectionUri, redirectUris)) {
return redirectionUri;
}
} else {
// Accept Request Without redirect_uri when One Registered
if (redirectUris != null && redirectUris.length == 1) {
return redirectUris[0];
}

//if not found a match, should it print a log ?
return redirectionUri.matches(client.getAttributes().getRedirectUrisRegex()) ? redirectionUri : null;
}
} catch (Exception e) {
return null;
Expand Down
Loading

0 comments on commit 6cfa087

Please sign in to comment.