disabledReason as read-only attribute, AuthenticatorUtils
This commit is contained in:
parent
315b9e3c29
commit
5a33ec2244
7 changed files with 89 additions and 58 deletions
|
@ -34,12 +34,12 @@ import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
import org.keycloak.representations.idm.CredentialRepresentation;
|
import org.keycloak.representations.idm.CredentialRepresentation;
|
||||||
import org.keycloak.services.ServicesLogger;
|
import org.keycloak.services.ServicesLogger;
|
||||||
import org.keycloak.services.managers.AuthenticationManager;
|
import org.keycloak.services.managers.AuthenticationManager;
|
||||||
import org.keycloak.services.managers.BruteForceProtector;
|
|
||||||
import org.keycloak.services.messages.Messages;
|
import org.keycloak.services.messages.Messages;
|
||||||
|
|
||||||
import javax.ws.rs.core.MultivaluedMap;
|
import javax.ws.rs.core.MultivaluedMap;
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
|
|
||||||
|
import static org.keycloak.authentication.authenticators.util.AuthenticatorUtils.getDisabledByBruteForceEventError;
|
||||||
import static org.keycloak.services.validation.Validation.FIELD_PASSWORD;
|
import static org.keycloak.services.validation.Validation.FIELD_PASSWORD;
|
||||||
import static org.keycloak.services.validation.Validation.FIELD_USERNAME;
|
import static org.keycloak.services.validation.Validation.FIELD_USERNAME;
|
||||||
|
|
||||||
|
@ -240,17 +240,13 @@ public abstract class AbstractUsernameFormAuthenticator extends AbstractFormAuth
|
||||||
}
|
}
|
||||||
|
|
||||||
protected boolean isDisabledByBruteForce(AuthenticationFlowContext context, UserModel user) {
|
protected boolean isDisabledByBruteForce(AuthenticationFlowContext context, UserModel user) {
|
||||||
if (context.getRealm().isBruteForceProtected()) {
|
String bruteForceError = getDisabledByBruteForceEventError(context.getProtector(), context.getSession(), context.getRealm(), user);
|
||||||
BruteForceProtector protector = context.getProtector();
|
if (bruteForceError != null) {
|
||||||
boolean isPermanentlyLockedOut = protector.isPermanentlyLockedOut(context.getSession(), context.getRealm(), user);
|
context.getEvent().user(user);
|
||||||
|
context.getEvent().error(bruteForceError);
|
||||||
if (isPermanentlyLockedOut || protector.isTemporarilyDisabled(context.getSession(), context.getRealm(), user)) {
|
Response challengeResponse = challenge(context, disabledByBruteForceError(), disabledByBruteForceFieldError());
|
||||||
context.getEvent().user(user);
|
context.forceChallenge(challengeResponse);
|
||||||
context.getEvent().error(isPermanentlyLockedOut ? Errors.USER_DISABLED : Errors.USER_TEMPORARILY_DISABLED);
|
return true;
|
||||||
Response challengeResponse = challenge(context, disabledByBruteForceError(), disabledByBruteForceFieldError());
|
|
||||||
context.forceChallenge(challengeResponse);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,13 +31,14 @@ import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
import org.keycloak.provider.ProviderConfigProperty;
|
import org.keycloak.provider.ProviderConfigProperty;
|
||||||
import org.keycloak.services.ServicesLogger;
|
import org.keycloak.services.ServicesLogger;
|
||||||
import org.keycloak.services.managers.AuthenticationManager;
|
import org.keycloak.services.managers.AuthenticationManager;
|
||||||
import org.keycloak.services.managers.BruteForceProtector;
|
|
||||||
|
|
||||||
import javax.ws.rs.core.MultivaluedMap;
|
import javax.ws.rs.core.MultivaluedMap;
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.keycloak.authentication.authenticators.util.AuthenticatorUtils.getDisabledByBruteForceEventError;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
|
@ -75,18 +76,16 @@ public class ValidateUsername extends AbstractDirectGrantAuthenticator {
|
||||||
context.failure(AuthenticationFlowError.INVALID_USER, challengeResponse);
|
context.failure(AuthenticationFlowError.INVALID_USER, challengeResponse);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (context.getRealm().isBruteForceProtected()) {
|
|
||||||
BruteForceProtector protector = context.getProtector();
|
|
||||||
boolean isPermanentlyLockedOut = protector.isPermanentlyLockedOut(context.getSession(), context.getRealm(), user);
|
|
||||||
|
|
||||||
if (isPermanentlyLockedOut || protector.isTemporarilyDisabled(context.getSession(), context.getRealm(), user)) {
|
String bruteForceError = getDisabledByBruteForceEventError(context.getProtector(), context.getSession(), context.getRealm(), user);
|
||||||
context.getEvent().user(user);
|
if (bruteForceError != null) {
|
||||||
context.getEvent().error(isPermanentlyLockedOut ? Errors.USER_DISABLED : Errors.USER_TEMPORARILY_DISABLED);
|
context.getEvent().user(user);
|
||||||
Response challengeResponse = errorResponse(Response.Status.UNAUTHORIZED.getStatusCode(), "invalid_grant", "Invalid user credentials");
|
context.getEvent().error(bruteForceError);
|
||||||
context.failure(AuthenticationFlowError.INVALID_USER, challengeResponse);
|
Response challengeResponse = errorResponse(Response.Status.UNAUTHORIZED.getStatusCode(), "invalid_grant", "Invalid user credentials");
|
||||||
return;
|
context.failure(AuthenticationFlowError.INVALID_USER, challengeResponse);
|
||||||
}
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!user.isEnabled()) {
|
if (!user.isEnabled()) {
|
||||||
context.getEvent().user(user);
|
context.getEvent().user(user);
|
||||||
context.getEvent().error(Errors.USER_DISABLED);
|
context.getEvent().error(Errors.USER_DISABLED);
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2021 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.keycloak.authentication.authenticators.util;
|
||||||
|
|
||||||
|
import org.keycloak.events.Errors;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
|
import org.keycloak.services.managers.BruteForceProtector;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Vaclav Muzikar <vmuzikar@redhat.com>
|
||||||
|
*/
|
||||||
|
public final class AuthenticatorUtils {
|
||||||
|
public static String getDisabledByBruteForceEventError(BruteForceProtector protector, KeycloakSession session, RealmModel realm, UserModel user) {
|
||||||
|
if (realm.isBruteForceProtected()) {
|
||||||
|
if (protector.isPermanentlyLockedOut(session, realm, user)) {
|
||||||
|
return Errors.USER_DISABLED;
|
||||||
|
}
|
||||||
|
else if (protector.isTemporarilyDisabled(session, realm, user)) {
|
||||||
|
return Errors.USER_TEMPORARILY_DISABLED;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
|
@ -30,7 +30,8 @@ import org.keycloak.events.Errors;
|
||||||
import org.keycloak.models.ModelDuplicateException;
|
import org.keycloak.models.ModelDuplicateException;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.services.ServicesLogger;
|
import org.keycloak.services.ServicesLogger;
|
||||||
import org.keycloak.services.managers.BruteForceProtector;
|
|
||||||
|
import static org.keycloak.authentication.authenticators.util.AuthenticatorUtils.getDisabledByBruteForceEventError;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:pnalyvayko@agi.com">Peter Nalyvayko</a>
|
* @author <a href="mailto:pnalyvayko@agi.com">Peter Nalyvayko</a>
|
||||||
|
@ -119,18 +120,16 @@ public class ValidateX509CertificateUsername extends AbstractX509ClientCertifica
|
||||||
context.failure(AuthenticationFlowError.INVALID_USER, challengeResponse);
|
context.failure(AuthenticationFlowError.INVALID_USER, challengeResponse);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (context.getRealm().isBruteForceProtected()) {
|
|
||||||
BruteForceProtector protector = context.getProtector();
|
|
||||||
boolean isPermanentlyLockedOut = protector.isPermanentlyLockedOut(context.getSession(), context.getRealm(), user);
|
|
||||||
|
|
||||||
if (isPermanentlyLockedOut || protector.isTemporarilyDisabled(context.getSession(), context.getRealm(), user)) {
|
String bruteForceError = getDisabledByBruteForceEventError(context.getProtector(), context.getSession(), context.getRealm(), user);
|
||||||
context.getEvent().user(user);
|
if (bruteForceError != null) {
|
||||||
context.getEvent().error(isPermanentlyLockedOut ? Errors.USER_DISABLED : Errors.USER_TEMPORARILY_DISABLED);
|
context.getEvent().user(user);
|
||||||
Response challengeResponse = errorResponse(Response.Status.BAD_REQUEST.getStatusCode(), "invalid_grant", "Invalid user credentials");
|
context.getEvent().error(bruteForceError);
|
||||||
context.failure(AuthenticationFlowError.INVALID_USER, challengeResponse);
|
Response challengeResponse = errorResponse(Response.Status.BAD_REQUEST.getStatusCode(), "invalid_grant", "Invalid user credentials");
|
||||||
return;
|
context.failure(AuthenticationFlowError.INVALID_USER, challengeResponse);
|
||||||
}
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!user.isEnabled()) {
|
if (!user.isEnabled()) {
|
||||||
context.getEvent().user(user);
|
context.getEvent().user(user);
|
||||||
context.getEvent().error(Errors.USER_DISABLED);
|
context.getEvent().error(Errors.USER_DISABLED);
|
||||||
|
|
|
@ -35,7 +35,8 @@ import org.keycloak.forms.login.LoginFormsProvider;
|
||||||
import org.keycloak.models.ModelDuplicateException;
|
import org.keycloak.models.ModelDuplicateException;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.utils.FormMessage;
|
import org.keycloak.models.utils.FormMessage;
|
||||||
import org.keycloak.services.managers.BruteForceProtector;
|
|
||||||
|
import static org.keycloak.authentication.authenticators.util.AuthenticatorUtils.getDisabledByBruteForceEventError;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:pnalyvayko@agi.com">Peter Nalyvayko</a>
|
* @author <a href="mailto:pnalyvayko@agi.com">Peter Nalyvayko</a>
|
||||||
|
@ -136,21 +137,17 @@ public class X509ClientCertificateAuthenticator extends AbstractX509ClientCertif
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (context.getRealm().isBruteForceProtected()) {
|
String bruteForceError = getDisabledByBruteForceEventError(context.getProtector(), context.getSession(), context.getRealm(), user);
|
||||||
BruteForceProtector protector = context.getProtector();
|
if (bruteForceError != null) {
|
||||||
boolean isPermanentlyLockedOut = protector.isPermanentlyLockedOut(context.getSession(), context.getRealm(), user);
|
context.getEvent().user(user);
|
||||||
|
context.getEvent().error(bruteForceError);
|
||||||
if (isPermanentlyLockedOut || protector.isTemporarilyDisabled(context.getSession(), context.getRealm(), user)) {
|
// TODO use specific locale to load error messages
|
||||||
context.getEvent().user(user);
|
String errorMessage = "X509 certificate authentication's failed.";
|
||||||
context.getEvent().error(isPermanentlyLockedOut ? Errors.USER_DISABLED : Errors.USER_TEMPORARILY_DISABLED);
|
// TODO is calling form().setErrors enough to show errors on login screen?
|
||||||
// TODO use specific locale to load error messages
|
context.challenge(createErrorResponse(context, certs[0].getSubjectDN().getName(),
|
||||||
String errorMessage = "X509 certificate authentication's failed.";
|
errorMessage, "Invalid user"));
|
||||||
// TODO is calling form().setErrors enough to show errors on login screen?
|
context.attempted();
|
||||||
context.challenge(createErrorResponse(context, certs[0].getSubjectDN().getName(),
|
return;
|
||||||
errorMessage, "Invalid user"));
|
|
||||||
context.attempted();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!userEnabled(context, user)) {
|
if (!userEnabled(context, user)) {
|
||||||
|
|
|
@ -136,6 +136,7 @@ import java.util.regex.Pattern;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import static org.keycloak.authentication.authenticators.util.AuthenticatorUtils.getDisabledByBruteForceEventError;
|
||||||
import static org.keycloak.models.ImpersonationSessionNote.IMPERSONATOR_ID;
|
import static org.keycloak.models.ImpersonationSessionNote.IMPERSONATOR_ID;
|
||||||
import static org.keycloak.models.ImpersonationSessionNote.IMPERSONATOR_USERNAME;
|
import static org.keycloak.models.ImpersonationSessionNote.IMPERSONATOR_USERNAME;
|
||||||
|
|
||||||
|
@ -1234,14 +1235,11 @@ public class TokenEndpoint {
|
||||||
event.error(Errors.USER_DISABLED);
|
event.error(Errors.USER_DISABLED);
|
||||||
throw new CorsErrorResponseException(cors, Errors.INVALID_TOKEN, "Invalid Token", Response.Status.BAD_REQUEST);
|
throw new CorsErrorResponseException(cors, Errors.INVALID_TOKEN, "Invalid Token", Response.Status.BAD_REQUEST);
|
||||||
}
|
}
|
||||||
if (realm.isBruteForceProtected()) {
|
|
||||||
BruteForceProtector protector = session.getProvider(BruteForceProtector.class);
|
|
||||||
boolean isPermanentlyLockedOut = protector.isPermanentlyLockedOut(session, realm, user);
|
|
||||||
|
|
||||||
if (isPermanentlyLockedOut || protector.isTemporarilyDisabled(session, realm, user)) {
|
String bruteForceError = getDisabledByBruteForceEventError(session.getProvider(BruteForceProtector.class), session, realm, user);
|
||||||
event.error(isPermanentlyLockedOut ? Errors.USER_DISABLED : Errors.USER_TEMPORARILY_DISABLED);
|
if (bruteForceError != null) {
|
||||||
throw new CorsErrorResponseException(cors, Errors.INVALID_TOKEN, "Invalid Token", Response.Status.BAD_REQUEST);
|
event.error(bruteForceError);
|
||||||
}
|
throw new CorsErrorResponseException(cors, Errors.INVALID_TOKEN, "Invalid Token", Response.Status.BAD_REQUEST);
|
||||||
}
|
}
|
||||||
|
|
||||||
context.getIdp().updateBrokeredUser(session, realm, user, context);
|
context.getIdp().updateBrokeredUser(session, realm, user, context);
|
||||||
|
|
|
@ -44,7 +44,7 @@ public class LegacyUserProfileProviderFactory implements UserProfileProviderFact
|
||||||
// Attributes, which can't be updated by administrator
|
// Attributes, which can't be updated by administrator
|
||||||
private Pattern adminReadOnlyAttributesPattern;
|
private Pattern adminReadOnlyAttributesPattern;
|
||||||
|
|
||||||
private String[] DEFAULT_READ_ONLY_ATTRIBUTES = { "KERBEROS_PRINCIPAL", "LDAP_ID", "LDAP_ENTRY_DN", "CREATED_TIMESTAMP", "createTimestamp", "modifyTimestamp", "userCertificate", "saml.persistent.name.id.for.*", "ENABLED", "EMAIL_VERIFIED" };
|
private String[] DEFAULT_READ_ONLY_ATTRIBUTES = { "KERBEROS_PRINCIPAL", "LDAP_ID", "LDAP_ENTRY_DN", "CREATED_TIMESTAMP", "createTimestamp", "modifyTimestamp", "userCertificate", "saml.persistent.name.id.for.*", "ENABLED", "EMAIL_VERIFIED", "disabledReason" };
|
||||||
private String[] DEFAULT_ADMIN_READ_ONLY_ATTRIBUTES = { "KERBEROS_PRINCIPAL", "LDAP_ID", "LDAP_ENTRY_DN", "CREATED_TIMESTAMP", "createTimestamp", "modifyTimestamp" };
|
private String[] DEFAULT_ADMIN_READ_ONLY_ATTRIBUTES = { "KERBEROS_PRINCIPAL", "LDAP_ID", "LDAP_ENTRY_DN", "CREATED_TIMESTAMP", "createTimestamp", "modifyTimestamp" };
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
Loading…
Reference in a new issue