Skip to content

Commit

Permalink
feat(jans-auth-server): check offline_access implementation has all c…
Browse files Browse the repository at this point in the history
…onditions defined in spec #1945 (#3004)
  • Loading branch information
yuriyz authored Nov 15, 2022
1 parent d6a5e99 commit af30e4c
Show file tree
Hide file tree
Showing 4 changed files with 179 additions and 50 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,17 @@ public class ClientAttributes implements Serializable {
@JsonProperty("idTokenLifetime")
private Integer idTokenLifetime;

@JsonProperty("allowOfflineAccessWithoutConsent")
private Boolean allowOfflineAccessWithoutConsent;

public Boolean getAllowOfflineAccessWithoutConsent() {
return allowOfflineAccessWithoutConsent;
}

public void setAllowOfflineAccessWithoutConsent(Boolean allowOfflineAccessWithoutConsent) {
this.allowOfflineAccessWithoutConsent = allowOfflineAccessWithoutConsent;
}

public Integer getIdTokenLifetime() {
return idTokenLifetime;
}
Expand Down Expand Up @@ -366,6 +377,7 @@ public String toString() {
", authorizationEncryptedResponseEnc=" + authorizationEncryptedResponseEnc +
", publicSubjectIdentifierAttribute=" + publicSubjectIdentifierAttribute +
", redirectUrisRegex=" + redirectUrisRegex +
", allowOfflineAccessWithoutConsent=" + allowOfflineAccessWithoutConsent +
", defaultPromptLogin=" + defaultPromptLogin +
'}';
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,13 @@
import com.google.common.collect.Maps;
import io.jans.as.common.model.common.User;
import io.jans.as.common.model.registration.Client;
import io.jans.as.common.model.session.SessionId;
import io.jans.as.common.model.session.SessionIdState;
import io.jans.as.common.util.RedirectUri;
import io.jans.as.model.authorize.AuthorizeErrorResponseType;
import io.jans.as.model.authorize.AuthorizeRequestParam;
import io.jans.as.model.authorize.AuthorizeResponseParam;
import io.jans.as.model.common.BackchannelTokenDeliveryMode;
import io.jans.as.model.common.GrantType;
import io.jans.as.model.common.Prompt;
import io.jans.as.model.common.ResponseMode;
import io.jans.as.model.common.ResponseType;
import io.jans.as.model.common.ScopeConstants;
import io.jans.as.model.common.SubjectType;
import io.jans.as.model.common.*;
import io.jans.as.model.configuration.AppConfiguration;
import io.jans.as.model.crypto.binding.TokenBindingMessage;
import io.jans.as.model.crypto.binding.TokenBindingParseException;
Expand All @@ -32,22 +28,7 @@
import io.jans.as.server.ciba.CIBAPushTokenDeliveryService;
import io.jans.as.server.model.authorize.AuthorizeParamsValidator;
import io.jans.as.server.model.authorize.ScopeChecker;
import io.jans.as.server.model.common.AccessToken;
import io.jans.as.server.model.common.AuthorizationCode;
import io.jans.as.server.model.common.AuthorizationGrant;
import io.jans.as.server.model.common.AuthorizationGrantList;
import io.jans.as.server.model.common.CIBAGrant;
import io.jans.as.server.model.common.CibaRequestCacheControl;
import io.jans.as.server.model.common.CibaRequestStatus;
import io.jans.as.server.model.common.DefaultScope;
import io.jans.as.server.model.common.DeviceAuthorizationCacheControl;
import io.jans.as.server.model.common.DeviceAuthorizationStatus;
import io.jans.as.server.model.common.DeviceCodeGrant;
import io.jans.as.server.model.common.ExecutionContext;
import io.jans.as.server.model.common.IdToken;
import io.jans.as.server.model.common.RefreshToken;
import io.jans.as.common.model.session.SessionId;
import io.jans.as.common.model.session.SessionIdState;
import io.jans.as.server.model.common.*;
import io.jans.as.server.model.config.ConfigurationFactory;
import io.jans.as.server.model.config.Constants;
import io.jans.as.server.model.exception.AcrChangedException;
Expand All @@ -56,15 +37,7 @@
import io.jans.as.server.model.ldap.ClientAuthorization;
import io.jans.as.server.model.token.JwrService;
import io.jans.as.server.security.Identity;
import io.jans.as.server.service.AttributeService;
import io.jans.as.server.service.AuthenticationFilterService;
import io.jans.as.server.service.ClientAuthorizationsService;
import io.jans.as.server.service.ClientService;
import io.jans.as.server.service.CookieService;
import io.jans.as.server.service.DeviceAuthorizationService;
import io.jans.as.server.service.RequestParameterService;
import io.jans.as.server.service.SessionIdService;
import io.jans.as.server.service.UserService;
import io.jans.as.server.service.*;
import io.jans.as.server.service.ciba.CibaRequestService;
import io.jans.as.server.service.external.ExternalPostAuthnService;
import io.jans.as.server.service.external.ExternalUpdateTokenService;
Expand Down Expand Up @@ -93,17 +66,12 @@
import org.slf4j.Logger;

import java.net.URI;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.Map.Entry;
import java.util.Set;
import java.util.function.Function;

import static io.jans.as.model.util.StringUtils.implode;
import static org.apache.commons.lang3.BooleanUtils.isTrue;
import static org.apache.commons.lang3.BooleanUtils.*;

/**
* Implementation for request authorization through REST web services.
Expand Down Expand Up @@ -365,7 +333,7 @@ private ResponseBuilder authorize(AuthzRequest authzRequest) throws AcrChangedEx

authzRequestService.setDefaultAcrsIfNeeded(authzRequest, client);

checkScopes(responseTypes, prompts, client, scopes);
checkOfflineAccessScopes(responseTypes, prompts, client, scopes);
checkResponseType(authzRequest, responseTypes, client);

AuthorizationGrant authorizationGrant = null;
Expand Down Expand Up @@ -688,17 +656,19 @@ private void validateMaxAge(AuthzRequest authzRequest, List<Prompt> prompts, Ses
}
}

private void checkScopes(List<ResponseType> responseTypes, List<Prompt> prompts, Client client, Set<String> scopes) {
if (scopes.contains(ScopeConstants.OFFLINE_ACCESS) && !client.getTrustedClient()) {
if (!responseTypes.contains(ResponseType.CODE)) {
log.trace("Removed (ignored) offline_scope. Can't find `code` in response_type which is required.");
scopes.remove(ScopeConstants.OFFLINE_ACCESS);
}
public void checkOfflineAccessScopes(List<ResponseType> responseTypes, List<Prompt> prompts, Client client, Set<String> scopes) {
if (!scopes.contains(ScopeConstants.OFFLINE_ACCESS) || client.getTrustedClient()) {
return;
}

if (scopes.contains(ScopeConstants.OFFLINE_ACCESS) && !prompts.contains(Prompt.CONSENT)) {
log.error("Removed offline_access. Can't find prompt=consent. Consent is required for offline_access.");
scopes.remove(ScopeConstants.OFFLINE_ACCESS);
}
if (!responseTypes.contains(ResponseType.CODE)) {
log.trace("Removed (ignored) offline_scope. Can't find `code` in response_type which is required.");
scopes.remove(ScopeConstants.OFFLINE_ACCESS);
}

if (scopes.contains(ScopeConstants.OFFLINE_ACCESS) && !prompts.contains(Prompt.CONSENT) && !toBoolean(client.getAttributes().getAllowOfflineAccessWithoutConsent())) {
log.error("Removed offline_access. Can't find prompt=consent. Consent is required for offline_access.");
scopes.remove(ScopeConstants.OFFLINE_ACCESS);
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
package io.jans.as.server.authorize.ws.rs;

import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import io.jans.as.common.model.registration.Client;
import io.jans.as.model.common.ResponseType;
import io.jans.as.model.common.ScopeConstants;
import io.jans.as.model.configuration.AppConfiguration;
import io.jans.as.model.error.ErrorResponseFactory;
import io.jans.as.server.audit.ApplicationAuditLogger;
import io.jans.as.server.ciba.CIBAPingCallbackService;
import io.jans.as.server.ciba.CIBAPushTokenDeliveryService;
import io.jans.as.server.model.authorize.ScopeChecker;
import io.jans.as.server.model.common.AuthorizationGrantList;
import io.jans.as.server.model.config.ConfigurationFactory;
import io.jans.as.server.security.Identity;
import io.jans.as.server.service.*;
import io.jans.as.server.service.ciba.CibaRequestService;
import io.jans.as.server.service.external.ExternalPostAuthnService;
import io.jans.as.server.service.external.ExternalUpdateTokenService;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.testng.MockitoTestNGListener;
import org.slf4j.Logger;
import org.testng.annotations.Listeners;
import org.testng.annotations.Test;

import java.util.Set;

import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertTrue;

/**
* @author Yuriy Z
*/
@Listeners(MockitoTestNGListener.class)
public class AuthorizeRestWebServiceImplTest {

@InjectMocks
private AuthorizeRestWebServiceImpl authorizeRestWebService;

@Mock
private Logger log;

@Mock
private ApplicationAuditLogger applicationAuditLogger;

@Mock
private ErrorResponseFactory errorResponseFactory;

@Mock
private AuthorizationGrantList authorizationGrantList;

@Mock
private ClientService clientService;

@Mock
private UserService userService;

@Mock
private Identity identity;

@Mock
private AuthenticationFilterService authenticationFilterService;

@Mock
private SessionIdService sessionIdService;

@Mock
private CookieService cookieService;

@Mock
private ScopeChecker scopeChecker;

@Mock
private ClientAuthorizationsService clientAuthorizationsService;

@Mock
private RequestParameterService requestParameterService;

@Mock
private AppConfiguration appConfiguration;

@Mock
private ConfigurationFactory configurationFactory;

@Mock
private AuthorizeRestWebServiceValidator authorizeRestWebServiceValidator;

@Mock
private CIBAPushTokenDeliveryService cibaPushTokenDeliveryService;

@Mock
private CIBAPingCallbackService cibaPingCallbackService;

@Mock
private ExternalPostAuthnService externalPostAuthnService;

@Mock
private CibaRequestService cibaRequestService;

@Mock
private DeviceAuthorizationService deviceAuthorizationService;

@Mock
private AttributeService attributeService;

@Mock
private ExternalUpdateTokenService externalUpdateTokenService;

@Mock
private AuthzRequestService authzRequestService;

@Test
public void checkOfflineAccessScopes_whenOfflineAccessIsPresentAndConsentNot_shouldRemoveOfflineAccess() {
final Set<String> scopes = Sets.newHashSet(ScopeConstants.OFFLINE_ACCESS);
authorizeRestWebService.checkOfflineAccessScopes(Lists.newArrayList(ResponseType.CODE), Lists.newArrayList(), new Client(), scopes);
assertTrue(scopes.isEmpty());
}

@Test
public void checkOfflineAccessScopes_whenOfflineAccessIsPresentAndConsentNotButAllowedByClient_shouldNotRemoveOfflineAccess() {
final Set<String> scopes = Sets.newHashSet(ScopeConstants.OFFLINE_ACCESS);
final Client client = new Client();
client.getAttributes().setAllowOfflineAccessWithoutConsent(true);

authorizeRestWebService.checkOfflineAccessScopes(Lists.newArrayList(ResponseType.CODE), Lists.newArrayList(), client, scopes);
assertEquals(scopes.iterator().next(), ScopeConstants.OFFLINE_ACCESS);
}

@Test
public void checkOfflineAccessScopes_whenOfflineAccessIsPresentAndResponseTypeCodeAbsent_shouldRemoveOfflineAccess() {
final Set<String> scopes = Sets.newHashSet(ScopeConstants.OFFLINE_ACCESS);

authorizeRestWebService.checkOfflineAccessScopes(Lists.newArrayList(ResponseType.TOKEN), Lists.newArrayList(), new Client(), scopes);
assertTrue(scopes.isEmpty());
}

@Test
public void checkOfflineAccessScopes_whenOfflineAccessIsPresentAndResponseTypeCodeAbsent_shouldRemoveOfflineAccessOnly() {
final Set<String> scopes = Sets.newHashSet("openid", ScopeConstants.OFFLINE_ACCESS);

authorizeRestWebService.checkOfflineAccessScopes(Lists.newArrayList(ResponseType.TOKEN), Lists.newArrayList(), new Client(), scopes);
assertEquals(scopes.iterator().next(), "openid");
}
}
1 change: 1 addition & 0 deletions jans-auth-server/server/src/test/resources/testng.xml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
<class name="io.jans.as.server.token.ws.rs.TokenRestWebServiceValidatorTest" />
<class name="io.jans.as.server.ws.rs.stat.MonthsTest" />
<class name="io.jans.as.server.authorize.ws.rs.AuthorizeRestWebServiceValidatorTest" />
<class name="io.jans.as.server.authorize.ws.rs.AuthorizeRestWebServiceImplTest" />
<class name="io.jans.as.server.session.ws.rs.EndSessionRestWebServiceImplTest" />
</classes>
</test>
Expand Down

0 comments on commit af30e4c

Please sign in to comment.