Skip to content

Commit

Permalink
feat(jans-auth-server): specify minimum acr for clients #343
Browse files Browse the repository at this point in the history
  • Loading branch information
yuriyz committed Nov 25, 2022
1 parent cc8b746 commit e2817bd
Show file tree
Hide file tree
Showing 5 changed files with 283 additions and 13 deletions.
3 changes: 2 additions & 1 deletion jans-auth-server/common/src/test/resources/testng.xml
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" >

<suite name="oxAuthCommon" parallel="false">
<test name="Audience Test" enabled="true">
<test name="Unit Tests" enabled="true">
<classes>
<class name="io.jans.as.common.AudienceTest"/>
<class name="io.jans.as.common.util.RedirectUriTest"/>
<class name="io.jans.as.common.model.registration.ClientSerializationTest"/>
<class name="io.jans.as.common.service.common.UserServiceTest"/>
<class name="io.jans.as.common.service.common.InumServiceTest"/>
</classes>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,41 @@ public class ClientAttributes implements Serializable {
@JsonProperty("allowOfflineAccessWithoutConsent")
private Boolean allowOfflineAccessWithoutConsent;

@JsonProperty("minimumAcrLevel")
private Integer minimumAcrLevel = -1;

@JsonProperty("minimumAcrLevelAutoresolve")
private Boolean minimumAcrLevelAutoresolve;

@JsonProperty("minimumAcrPriorityList")
private List<String> minimumAcrPriorityList;

public Boolean getMinimumAcrLevelAutoresolve() {
return minimumAcrLevelAutoresolve;
}

public void setMinimumAcrLevelAutoresolve(Boolean minimumAcrLevelAutoresolve) {
this.minimumAcrLevelAutoresolve = minimumAcrLevelAutoresolve;
}

public List<String> getMinimumAcrPriorityList() {
if (minimumAcrPriorityList == null) minimumAcrPriorityList = new ArrayList<>();
return minimumAcrPriorityList;
}

public void setMinimumAcrPriorityList(List<String> minimumAcrPriorityList) {
this.minimumAcrPriorityList = minimumAcrPriorityList;
}

public Integer getMinimumAcrLevel() {
if (minimumAcrLevel == null) minimumAcrLevel = -1;
return minimumAcrLevel;
}

public void setMinimumAcrLevel(Integer minimumAcrLevel) {
this.minimumAcrLevel = minimumAcrLevel;
}

public Boolean getAllowOfflineAccessWithoutConsent() {
return allowOfflineAccessWithoutConsent;
}
Expand Down Expand Up @@ -378,6 +413,9 @@ public String toString() {
", publicSubjectIdentifierAttribute=" + publicSubjectIdentifierAttribute +
", redirectUrisRegex=" + redirectUrisRegex +
", allowOfflineAccessWithoutConsent=" + allowOfflineAccessWithoutConsent +
", minimumAcrLevel=" + minimumAcrLevel +
", minimumAcrLevelAutoresolve=" + minimumAcrLevelAutoresolve +
", minimumAcrPriorityList=" + minimumAcrPriorityList +
", defaultPromptLogin=" + defaultPromptLogin +
'}';
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -331,7 +331,7 @@ private ResponseBuilder authorize(AuthzRequest authzRequest) throws AcrChangedEx
authorizeRestWebServiceValidator.validate(authzRequest, responseTypes, client);
authorizeRestWebServiceValidator.validatePkce(authzRequest.getCodeChallenge(), authzRequest.getRedirectUriResponse());

authzRequestService.setDefaultAcrsIfNeeded(authzRequest, client);
authzRequestService.setAcrsIfNeeded(authzRequest);

checkOfflineAccessScopes(responseTypes, prompts, client, scopes);
checkResponseType(authzRequest, responseTypes, client);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package io.jans.as.server.authorize.ws.rs;

import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import io.jans.as.common.model.common.User;
Expand Down Expand Up @@ -41,6 +43,7 @@
import io.jans.as.server.model.token.HandleTokenFactory;
import io.jans.as.server.par.ws.rs.ParService;
import io.jans.as.server.service.*;
import io.jans.as.server.service.external.ExternalAuthenticationService;
import io.jans.as.server.util.ServerUtil;
import jakarta.ejb.Stateless;
import jakarta.inject.Inject;
Expand All @@ -58,10 +61,8 @@
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.security.PrivateKey;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Set;
import java.util.*;
import java.util.concurrent.TimeUnit;

import static io.jans.as.model.util.StringUtils.implode;
import static org.apache.commons.lang3.BooleanUtils.isTrue;
Expand All @@ -74,6 +75,8 @@
public class AuthzRequestService {

public static final String INVALID_JWT_AUTHORIZATION_REQUEST = "Invalid JWT authorization request";
private static final long ACR_TO_LEVEL_CACHE_LIFETIME_IN_MINUTES = 15;
private static final String ACR_TO_LEVEL_KEY = "ACR_TO_LEVEL_KEY";

@Inject
private Logger log;
Expand Down Expand Up @@ -108,6 +111,22 @@ public class AuthzRequestService {
@Inject
private RedirectionUriService redirectionUriService;

@Inject
private ExternalAuthenticationService externalAuthenticationService;

private final Cache<String, Map<String, Integer>> acrToLevelCache = CacheBuilder.newBuilder()
.expireAfterWrite(ACR_TO_LEVEL_CACHE_LIFETIME_IN_MINUTES, TimeUnit.MINUTES).build();

public Map<String, Integer> getAcrToLevelMap() {
Map<String, Integer> map = acrToLevelCache.getIfPresent(ACR_TO_LEVEL_KEY);
if (map != null) {
return map;
}
map = externalAuthenticationService.acrToLevelMapping();
acrToLevelCache.put(ACR_TO_LEVEL_KEY, map);
return map;
}

public void addDeviceSecretToSession(AuthzRequest authzRequest, SessionId sessionId) {
if (BooleanUtils.isFalse(appConfiguration.getReturnDeviceSecretFromAuthzEndpoint())) {
return;
Expand Down Expand Up @@ -459,10 +478,73 @@ public void fillRedirectUriResponseforJARM(RedirectUriResponse redirectUriRespon
}
}

public void setDefaultAcrsIfNeeded(AuthzRequest authzRequest, Client client) {
if (StringUtils.isBlank(authzRequest.getAcrValues()) && !ArrayUtils.isEmpty(client.getDefaultAcrValues())) {
authzRequest.setAcrValues(implode(client.getDefaultAcrValues(), " "));
public void setAcrsIfNeeded(AuthzRequest authzRequest) {
Client client = authzRequest.getClient();

// explicitly set acrs via getDefaultAcrValues()
if (StringUtils.isBlank(authzRequest.getAcrValues())) {
if (!ArrayUtils.isEmpty(client.getDefaultAcrValues())) {
authzRequest.setAcrValues(implode(client.getDefaultAcrValues(), " "));
}
return;
}

final int currentMinAcrLevel = getCurrentMinAcrLevel(authzRequest);
if (currentMinAcrLevel >= client.getAttributes().getMinimumAcrLevel()) {
return; // do nothing -> current level is enough
}

if (BooleanUtils.isNotTrue(client.getAttributes().getMinimumAcrLevelAutoresolve())) {
log.error("Current acr level is less then minimum required. currentMinAcrLevel: {}, clientMinAcrLevel: {}", currentMinAcrLevel, client.getAttributes().getMinimumAcrLevel());
throw new WebApplicationException(Response
.status(Response.Status.BAD_REQUEST)
.entity(errorResponseFactory.getErrorAsJson(AuthorizeErrorResponseType.INVALID_REQUEST, authzRequest.getState(), "Current acr level is less then minimum required by client"))
.build());
}

final Map<String, Integer> acrToLevelMap = getAcrToLevelMap();
if (client.getAttributes().getMinimumAcrPriorityList().isEmpty()) { // no priority list -> pick up next higher then minimum
for (Map.Entry<String, Integer> entry : acrToLevelMap.entrySet()) {
if (currentMinAcrLevel < entry.getValue()) {
authzRequest.setAcrValues(entry.getKey());
return;
}
}
}

for (String acr : client.getAttributes().getMinimumAcrPriorityList()) {
final Integer acrLevel = acrToLevelMap.get(acr);
if (acrLevel != null && acrLevel >= currentMinAcrLevel) {
authzRequest.setAcrValues(acr);
return;
}
}

log.error("Current acr level is less then minimum required by client. currentMinAcrLevel: {}, clientAttributes: {}", currentMinAcrLevel, client.getAttributes());
throw new WebApplicationException(Response
.status(Response.Status.BAD_REQUEST)
.entity(errorResponseFactory.getErrorAsJson(AuthorizeErrorResponseType.INVALID_REQUEST, authzRequest.getState(), "Current acr level is less then minimum required by client:" + client.getClientId()))
.build());
}

public int getCurrentMinAcrLevel(AuthzRequest authzRequest) {
if (StringUtils.isBlank(authzRequest.getAcrValues())) {
return -1;
}

Integer currentLevel = null;
final Map<String, Integer> acrToLevelMap = getAcrToLevelMap();
for (String acr : authzRequest.getAcrValuesList()) {
Integer level = acrToLevelMap.get(acr);
if (currentLevel == null) {
currentLevel = level;
continue;
}
if (level != null && level < currentLevel) {
currentLevel = level;
}
}
return currentLevel != null ? currentLevel : -1;
}

public void createRedirectUriResponse(AuthzRequest authzRequest) {
Expand Down
Loading

0 comments on commit e2817bd

Please sign in to comment.