Skip to content

Commit

Permalink
feat(jans-auth-server): support unmet_authentication_requirements err…
Browse files Browse the repository at this point in the history
…or code #7900 (#8189)

* feat(jans-auth-server): support unmet_authentication_requirements error code #7900

Signed-off-by: YuriyZ <yzabrovarniy@gmail.com>

* feat(jans-auth-server): return unmet_authentication_requirements error code if acr is not recognized #7900

Signed-off-by: YuriyZ <yzabrovarniy@gmail.com>

* feat(jans-auth-server): added test when unmet_authentication_requirements error code is returned #7900

Signed-off-by: YuriyZ <yzabrovarniy@gmail.com>

* docs(jans-auth-server): documented unmet_authentication_requirements #7900

Signed-off-by: YuriyZ <yzabrovarniy@gmail.com>

---------

Signed-off-by: YuriyZ <yzabrovarniy@gmail.com>
  • Loading branch information
yuriyz committed Apr 3, 2024
1 parent a80e9e3 commit 75a9cd3
Show file tree
Hide file tree
Showing 7 changed files with 94 additions and 1 deletion.
1 change: 1 addition & 0 deletions docs/admin/auth-server/oauth-features/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ The [Janssen Authentication Server](https://github.com/JanssenProject/jans/tree/
- JWT Secured Authorization Response Mode for OAuth 2.0 (JARM) [(spec)](https://openid.net/specs/oauth-v2-jarm.html)
- OAuth 2.0 for First-Party Native Applications [(spec draft)](https://www.ietf.org/archive/id/draft-parecki-oauth-first-party-native-apps-00.html)
- The Use of Attestation in OAuth 2.0 Dynamic Client Registration [(spec draft)](https://www.ietf.org/id/draft-tschofenig-oauth-attested-dclient-reg-00.html)
- OpenID Connect Core Error Code unmet_authentication_requirements [(spec)](https://openid.net/specs/openid-connect-unmet-authentication-requirements-1_0.html)


## Protocol Overview
Expand Down
8 changes: 8 additions & 0 deletions docs/admin/auth-server/openid-features/acrs.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,14 @@ flowchart TD
to any reason, then the Janssen Server uses the [internal server ACR](#1-internal-janssen-server-acr) to authenticate
the end-user.

## Errors

### unmet_authentication_requirements

If authorization request is sent to Authorization Endpoint with `acr_values` for which
AS it not able to find "Person Authentication" custom script, it returns "unmet_authentication_requirements"
with detail log in `jans-auth.log`.

## Want to contribute?

If you have content you'd like to contribute to this page in the meantime, you can get started with our [Contribution guide](https://docs.jans.io/head/CONTRIBUTING/).
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@
*/
public enum AuthorizeErrorResponseType implements IErrorType {

/**
* The Authorization Server is unable to meet the requirements of the Relying Party for
* the authentication of the End-User. OP is unable to use acr specified in request.
*/
UNMET_AUTHENTICATION_REQUIREMENTS("unmet_authentication_requirements"),

/**
* "request" parameter is supported by AS. But if it's switched off in configuration by setting
* requestParameterSupported=false then this error is returned from authorization endpoint.
Expand Down
5 changes: 5 additions & 0 deletions jans-auth-server/server/conf/jans-errors.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
{
"authorize":[
{
"id": "unmet_authentication_requirements",
"description": "The Authorization Server is unable to meet the requirements of the Relying Party for the authentication of the End-User. OP is unable to use acr specified in request.",
"uri": null
},
{
"id":"invalid_request",
"description":"The request is missing a required parameter, includes an unsupported parameter or parameter value, or is otherwise malformed.",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,31 +7,35 @@
package io.jans.as.server.authorize.ws.rs;

import com.google.common.base.Strings;
import io.jans.as.model.authzdetails.AuthzDetails;
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.authzdetails.AuthzDetails;
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.configuration.AppConfiguration;
import io.jans.as.model.crypto.signature.SignatureAlgorithm;
import io.jans.as.model.error.ErrorResponseFactory;
import io.jans.as.model.exception.InvalidJwtException;
import io.jans.as.model.util.Util;
import io.jans.as.server.model.authorize.AuthorizeParamsValidator;
import io.jans.as.server.model.authorize.JwtAuthorizationRequest;
import io.jans.as.server.model.common.DeviceAuthorizationCacheControl;
import io.jans.as.server.model.exception.AcrChangedException;
import io.jans.as.server.model.exception.InvalidRedirectUrlException;
import io.jans.as.server.security.Identity;
import io.jans.as.server.service.*;
import io.jans.as.server.service.external.ExternalAuthenticationService;
import io.jans.as.server.service.external.ExternalAuthzDetailTypeService;
import io.jans.as.server.service.external.session.SessionEvent;
import io.jans.as.server.service.external.session.SessionEventType;
import io.jans.as.server.util.RedirectUtil;
import io.jans.as.server.util.ServerUtil;
import io.jans.model.AuthenticationScriptUsageType;
import io.jans.model.custom.script.conf.CustomScriptConfiguration;
import io.jans.orm.exception.EntryPersistenceException;
import jakarta.ejb.Stateless;
import jakarta.inject.Inject;
Expand Down Expand Up @@ -88,6 +92,9 @@ public class AuthorizeRestWebServiceValidator {
@Inject
private ExternalAuthzDetailTypeService externalAuthzDetailTypeService;

@Inject
private ExternalAuthenticationService externalAuthenticationService;

public Client validateClient(String clientId, String state) {
return validateClient(clientId, state, false);
}
Expand Down Expand Up @@ -405,9 +412,24 @@ public void validateAcrs(AuthzRequest authzRequest, Client client) throws AcrCha
throw authzRequest.getRedirectUriResponse().createWebException(AuthorizeErrorResponseType.INVALID_REQUEST,
"Restricted acr value request, please review the list of authorized acr values for this client");
}

checkAcrScriptIsAvailable(authzRequest);
checkAcrChanged(authzRequest, identity.getSessionId()); // check after redirect uri is validated
}

public void checkAcrScriptIsAvailable(AuthzRequest authzRequest) {
if (Util.isBuiltInPasswordAuthn(authzRequest.getAcrValues())) {
return; // no need for script for built-in "simple_password_auth"
}

CustomScriptConfiguration script = externalAuthenticationService.determineCustomScriptConfiguration(AuthenticationScriptUsageType.INTERACTIVE, authzRequest.getAcrValuesList());
if (script == null) {
String msg = String.format("Unable to find script for acr: %s. Send error: %s",
authzRequest.getAcrValues(), AuthorizeErrorResponseType.UNMET_AUTHENTICATION_REQUIREMENTS.getParameter());
log.debug(msg);
throw authzRequest.getRedirectUriResponse().createWebException(AuthorizeErrorResponseType.UNMET_AUTHENTICATION_REQUIREMENTS, msg);
}
}

private void checkAcrChanged(AuthzRequest authzRequest, SessionId sessionUser) throws AcrChangedException {
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,12 @@
import io.jans.as.model.error.ErrorResponseFactory;
import io.jans.as.server.security.Identity;
import io.jans.as.server.service.*;
import io.jans.as.server.service.external.ExternalAuthenticationService;
import io.jans.as.server.service.external.ExternalAuthzDetailTypeService;
import io.jans.model.AuthenticationScriptUsageType;
import io.jans.model.custom.script.conf.CustomScriptConfiguration;
import io.jans.model.custom.script.model.CustomScript;
import io.jans.model.custom.script.type.auth.DummyPersonAuthenticationType;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.ws.rs.WebApplicationException;
import org.mockito.InjectMocks;
Expand All @@ -18,6 +23,7 @@
import org.testng.annotations.Test;

import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;

import static org.mockito.Mockito.*;
Expand Down Expand Up @@ -59,6 +65,46 @@ public class AuthorizeRestWebServiceValidatorTest {
@Mock
private ExternalAuthzDetailTypeService externalAuthzDetailTypeService;

@Mock
private ExternalAuthenticationService externalAuthenticationService;

@Test
public void checkAcrScriptIsAvailable_forBuildInAcr_shouldPass() {
AuthzRequest authzRequest = new AuthzRequest();
authzRequest.setAcrValues("simple_password_auth");

authorizeRestWebServiceValidator.checkAcrScriptIsAvailable(authzRequest);
}

@Test
public void checkAcrScriptIsAvailable_whenScriptIsAvailable_shouldPass() {
AuthzRequest authzRequest = new AuthzRequest();
authzRequest.setAcrValues("my_acr");

final CustomScriptConfiguration script = new CustomScriptConfiguration(new CustomScript(), new DummyPersonAuthenticationType(), new HashMap<>());
when(externalAuthenticationService.determineCustomScriptConfiguration(AuthenticationScriptUsageType.INTERACTIVE, authzRequest.getAcrValuesList())).thenReturn(script);

authorizeRestWebServiceValidator.checkAcrScriptIsAvailable(authzRequest);
}

@Test
public void checkAcrScriptIsAvailable_whenScriptIsNotAvailable_shouldFail() {
RedirectUri redirectUri = mock(RedirectUri.class);
when(redirectUri.toString()).thenReturn("http://rp.com");

AuthzRequest authzRequest = new AuthzRequest();
authzRequest.setAcrValues("my_acr");
authzRequest.setRedirectUriResponse(new RedirectUriResponse(redirectUri, "", mock(HttpServletRequest.class), mock(ErrorResponseFactory.class)));

try {
authorizeRestWebServiceValidator.checkAcrScriptIsAvailable(authzRequest);
} catch (WebApplicationException e) {
return;
}

fail("Script is not available but exception is not thrown.");
}

@Test
public void validateRequestParameterSupported_whenRequestIsEmpty_shouldPass() {
AuthzRequest authzRequest = new AuthzRequest();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
{
"authorize":[
{
"id": "unmet_authentication_requirements",
"description": "The Authorization Server is unable to meet the requirements of the Relying Party for the authentication of the End-User. OP is unable to use acr specified in request.",
"uri": null
},
{
"id":"invalid_request",
"description":"The request is missing a required parameter, includes an unsupported parameter or parameter value, or is otherwise malformed.",
Expand Down

0 comments on commit 75a9cd3

Please sign in to comment.