Introduce profile 'feature' for step-up authentication enabled by default
Closes #10315
This commit is contained in:
parent
48565832d4
commit
d394e51674
6 changed files with 47 additions and 6 deletions
|
@ -65,7 +65,8 @@ public class Profile {
|
||||||
MAP_STORAGE("New store", Type.EXPERIMENTAL),
|
MAP_STORAGE("New store", Type.EXPERIMENTAL),
|
||||||
PAR("OAuth 2.0 Pushed Authorization Requests (PAR)", Type.DEFAULT),
|
PAR("OAuth 2.0 Pushed Authorization Requests (PAR)", Type.DEFAULT),
|
||||||
DECLARATIVE_USER_PROFILE("Configure user profiles using a declarative style", Type.PREVIEW),
|
DECLARATIVE_USER_PROFILE("Configure user profiles using a declarative style", Type.PREVIEW),
|
||||||
DYNAMIC_SCOPES("Dynamic OAuth 2.0 scopes", Type.EXPERIMENTAL);
|
DYNAMIC_SCOPES("Dynamic OAuth 2.0 scopes", Type.EXPERIMENTAL),
|
||||||
|
STEP_UP_AUTHENTICATION("Step-up Authentication", Type.DEFAULT);
|
||||||
|
|
||||||
private String label;
|
private String label;
|
||||||
private final Type typeProject;
|
private final Type typeProject;
|
||||||
|
|
|
@ -21,13 +21,15 @@ import java.util.List;
|
||||||
import org.keycloak.Config;
|
import org.keycloak.Config;
|
||||||
import org.keycloak.authentication.AuthenticationFlowCallbackFactory;
|
import org.keycloak.authentication.AuthenticationFlowCallbackFactory;
|
||||||
import org.keycloak.authentication.Authenticator;
|
import org.keycloak.authentication.Authenticator;
|
||||||
|
import org.keycloak.common.Profile;
|
||||||
import org.keycloak.models.AuthenticationExecutionModel;
|
import org.keycloak.models.AuthenticationExecutionModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.KeycloakSessionFactory;
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
|
import org.keycloak.provider.EnvironmentDependentProviderFactory;
|
||||||
import org.keycloak.provider.ProviderConfigProperty;
|
import org.keycloak.provider.ProviderConfigProperty;
|
||||||
import org.keycloak.provider.ProviderConfigurationBuilder;
|
import org.keycloak.provider.ProviderConfigurationBuilder;
|
||||||
|
|
||||||
public class ConditionalLoaAuthenticatorFactory implements ConditionalAuthenticatorFactory, AuthenticationFlowCallbackFactory {
|
public class ConditionalLoaAuthenticatorFactory implements ConditionalAuthenticatorFactory, AuthenticationFlowCallbackFactory, EnvironmentDependentProviderFactory {
|
||||||
|
|
||||||
public static final String PROVIDER_ID = "conditional-level-of-authentication";
|
public static final String PROVIDER_ID = "conditional-level-of-authentication";
|
||||||
private static final AuthenticationExecutionModel.Requirement[] REQUIREMENT_CHOICES = new AuthenticationExecutionModel.Requirement[]{
|
private static final AuthenticationExecutionModel.Requirement[] REQUIREMENT_CHOICES = new AuthenticationExecutionModel.Requirement[]{
|
||||||
|
@ -105,4 +107,9 @@ public class ConditionalLoaAuthenticatorFactory implements ConditionalAuthentica
|
||||||
// NOP - instance created in create() method
|
// NOP - instance created in create() method
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSupported() {
|
||||||
|
return Profile.isFeatureEnabled(Profile.Feature.STEP_UP_AUTHENTICATION);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -884,7 +884,13 @@ public class TokenManager {
|
||||||
token.setNonce(clientSessionCtx.getAttribute(OIDCLoginProtocol.NONCE_PARAM, String.class));
|
token.setNonce(clientSessionCtx.getAttribute(OIDCLoginProtocol.NONCE_PARAM, String.class));
|
||||||
token.setScope(clientSessionCtx.getScopeString());
|
token.setScope(clientSessionCtx.getScopeString());
|
||||||
|
|
||||||
token.setAcr(getAcr(clientSession));
|
if (Profile.isFeatureEnabled(Profile.Feature.STEP_UP_AUTHENTICATION)) {
|
||||||
|
token.setAcr(getAcr(clientSession));
|
||||||
|
} else {
|
||||||
|
// Backwards compatibility behaviour prior step-up authentication was introduced
|
||||||
|
String acr = AuthenticationManager.isSSOAuthentication(clientSession) ? "0" : "1";
|
||||||
|
token.setAcr(acr);
|
||||||
|
}
|
||||||
|
|
||||||
String authTime = session.getNote(AuthenticationManager.AUTH_TIME);
|
String authTime = session.getNote(AuthenticationManager.AUTH_TIME);
|
||||||
if (authTime != null) {
|
if (authTime != null) {
|
||||||
|
|
|
@ -31,11 +31,13 @@ import org.junit.Assert;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
import org.keycloak.OAuth2Constants;
|
||||||
import org.keycloak.admin.client.resource.ClientResource;
|
import org.keycloak.admin.client.resource.ClientResource;
|
||||||
import org.keycloak.authentication.authenticators.browser.OTPFormAuthenticatorFactory;
|
import org.keycloak.authentication.authenticators.browser.OTPFormAuthenticatorFactory;
|
||||||
import org.keycloak.authentication.authenticators.browser.UsernamePasswordFormFactory;
|
import org.keycloak.authentication.authenticators.browser.UsernamePasswordFormFactory;
|
||||||
import org.keycloak.authentication.authenticators.conditional.ConditionalLoaAuthenticator;
|
import org.keycloak.authentication.authenticators.conditional.ConditionalLoaAuthenticator;
|
||||||
import org.keycloak.authentication.authenticators.conditional.ConditionalLoaAuthenticatorFactory;
|
import org.keycloak.authentication.authenticators.conditional.ConditionalLoaAuthenticatorFactory;
|
||||||
|
import org.keycloak.common.Profile;
|
||||||
import org.keycloak.events.Details;
|
import org.keycloak.events.Details;
|
||||||
import org.keycloak.models.AuthenticationExecutionModel.Requirement;
|
import org.keycloak.models.AuthenticationExecutionModel.Requirement;
|
||||||
import org.keycloak.models.Constants;
|
import org.keycloak.models.Constants;
|
||||||
|
@ -44,6 +46,7 @@ import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper;
|
||||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||||
import org.keycloak.representations.ClaimsRepresentation;
|
import org.keycloak.representations.ClaimsRepresentation;
|
||||||
import org.keycloak.representations.IDToken;
|
import org.keycloak.representations.IDToken;
|
||||||
|
import org.keycloak.representations.idm.AuthenticationFlowRepresentation;
|
||||||
import org.keycloak.representations.idm.ClientRepresentation;
|
import org.keycloak.representations.idm.ClientRepresentation;
|
||||||
import org.keycloak.representations.idm.EventRepresentation;
|
import org.keycloak.representations.idm.EventRepresentation;
|
||||||
import org.keycloak.representations.idm.RealmRepresentation;
|
import org.keycloak.representations.idm.RealmRepresentation;
|
||||||
|
@ -51,9 +54,12 @@ import org.keycloak.representations.idm.UserRepresentation;
|
||||||
import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
|
import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
|
||||||
import org.keycloak.testsuite.AssertEvents;
|
import org.keycloak.testsuite.AssertEvents;
|
||||||
import org.keycloak.testsuite.admin.ApiUtil;
|
import org.keycloak.testsuite.admin.ApiUtil;
|
||||||
|
import org.keycloak.testsuite.admin.authentication.AbstractAuthenticationTest;
|
||||||
import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude;
|
import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude;
|
||||||
|
import org.keycloak.testsuite.arquillian.annotation.DisableFeature;
|
||||||
import org.keycloak.testsuite.authentication.PushButtonAuthenticatorFactory;
|
import org.keycloak.testsuite.authentication.PushButtonAuthenticatorFactory;
|
||||||
import org.keycloak.testsuite.client.KeycloakTestingClient;
|
import org.keycloak.testsuite.client.KeycloakTestingClient;
|
||||||
|
import org.keycloak.testsuite.pages.AppPage;
|
||||||
import org.keycloak.testsuite.pages.ErrorPage;
|
import org.keycloak.testsuite.pages.ErrorPage;
|
||||||
import org.keycloak.testsuite.pages.LoginPage;
|
import org.keycloak.testsuite.pages.LoginPage;
|
||||||
import org.keycloak.testsuite.pages.LoginTotpPage;
|
import org.keycloak.testsuite.pages.LoginTotpPage;
|
||||||
|
@ -66,6 +72,7 @@ import org.keycloak.testsuite.util.WaitUtils;
|
||||||
import org.keycloak.util.JsonSerialization;
|
import org.keycloak.util.JsonSerialization;
|
||||||
|
|
||||||
import static org.hamcrest.CoreMatchers.is;
|
import static org.hamcrest.CoreMatchers.is;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude.AuthServer.REMOTE;
|
import static org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude.AuthServer.REMOTE;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -617,6 +624,26 @@ public class LevelOfAssuranceFlowTest extends AbstractTestRealmKeycloakTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisableFeature(value = Profile.Feature.STEP_UP_AUTHENTICATION, skipRestart = true)
|
||||||
|
public void testDisableStepupFeatureTest() {
|
||||||
|
BrowserFlowTest.revertFlows(testRealm(), "browser - Level of Authentication FLow");
|
||||||
|
|
||||||
|
// Login normal way - should return 1 (backwards compatibility before step-up was introduced)
|
||||||
|
loginPage.open();
|
||||||
|
authenticateWithUsernamePassword();
|
||||||
|
authenticateWithTotp(); // OTP required due the user has it
|
||||||
|
assertLoggedInWithAcr("1");
|
||||||
|
|
||||||
|
// SSO login - should return 0 (backwards compatibility before step-up was introduced)
|
||||||
|
oauth.openLoginForm();
|
||||||
|
assertLoggedInWithAcr("0");
|
||||||
|
|
||||||
|
// Flow is needed due the "after()" method
|
||||||
|
testingClient.server(TEST_REALM_NAME).run(session -> FlowUtil.inCurrentRealm(session).copyBrowserFlow("browser - Level of Authentication FLow"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public void openLoginFormWithAcrClaim(boolean essential, String... acrValues) {
|
public void openLoginFormWithAcrClaim(boolean essential, String... acrValues) {
|
||||||
openLoginFormWithAcrClaim(oauth, essential, acrValues);
|
openLoginFormWithAcrClaim(oauth, essential, acrValues);
|
||||||
}
|
}
|
||||||
|
|
|
@ -932,7 +932,7 @@
|
||||||
<kc-tooltip>{{:: 'require-pushed-authorization-requests.tooltip' | translate}}</kc-tooltip>
|
<kc-tooltip>{{:: 'require-pushed-authorization-requests.tooltip' | translate}}</kc-tooltip>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group clearfix block" data-ng-show="!clientEdit.bearerOnly && protocol == 'openid-connect'">
|
<div class="form-group clearfix block" data-ng-show="!clientEdit.bearerOnly && protocol == 'openid-connect' && serverInfo.featureEnabled('STEP_UP_AUTHENTICATION')">
|
||||||
<label class="col-md-2 control-label" for="newAcr">{{:: 'acr-loa-map' | translate}}</label>
|
<label class="col-md-2 control-label" for="newAcr">{{:: 'acr-loa-map' | translate}}</label>
|
||||||
<div class="col-sm-6">
|
<div class="col-sm-6">
|
||||||
<div class="input-group input-map" ng-repeat="(acr, loa) in acrLoaMap">
|
<div class="input-group input-map" ng-repeat="(acr, loa) in acrLoaMap">
|
||||||
|
@ -953,7 +953,7 @@
|
||||||
<kc-tooltip>{{:: 'acr-loa-map-client.tooltip' | translate}}</kc-tooltip>
|
<kc-tooltip>{{:: 'acr-loa-map-client.tooltip' | translate}}</kc-tooltip>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group" data-ng-show="!clientEdit.bearerOnly && protocol == 'openid-connect'">
|
<div class="form-group" data-ng-show="!clientEdit.bearerOnly && protocol == 'openid-connect' && serverInfo.featureEnabled('STEP_UP_AUTHENTICATION')">
|
||||||
<label class="col-md-2 control-label" for="newDefaultAcrValue">{{:: 'default-acr-values' | translate}}</label>
|
<label class="col-md-2 control-label" for="newDefaultAcrValue">{{:: 'default-acr-values' | translate}}</label>
|
||||||
|
|
||||||
<div class="col-sm-6">
|
<div class="col-sm-6">
|
||||||
|
|
|
@ -72,7 +72,7 @@
|
||||||
</div>
|
</div>
|
||||||
<kc-tooltip>{{:: 'sslRequired.tooltip' | translate}}</kc-tooltip>
|
<kc-tooltip>{{:: 'sslRequired.tooltip' | translate}}</kc-tooltip>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group clearfix block">
|
<div class="form-group clearfix block" data-ng-show="serverInfo.featureEnabled('STEP_UP_AUTHENTICATION')">
|
||||||
<label class="col-md-2 control-label" for="newAcr">{{:: 'acr-loa-map' | translate}}</label>
|
<label class="col-md-2 control-label" for="newAcr">{{:: 'acr-loa-map' | translate}}</label>
|
||||||
<div class="col-sm-6">
|
<div class="col-sm-6">
|
||||||
<div class="input-group input-map" ng-repeat="(acr, loa) in acrLoaMap">
|
<div class="input-group input-map" ng-repeat="(acr, loa) in acrLoaMap">
|
||||||
|
|
Loading…
Reference in a new issue