KEYCLOAK-1083: Provide a way for admin to unlock user account
This commit is contained in:
parent
0095968a73
commit
d6e64a2c5e
9 changed files with 56 additions and 13 deletions
|
@ -60,4 +60,11 @@ public class UsernameLoginFailureEntity extends AbstractIdentifiableEntity {
|
||||||
public void setRealmId(String realmId) {
|
public void setRealmId(String realmId) {
|
||||||
this.realmId = realmId;
|
this.realmId = realmId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void clearFailures() {
|
||||||
|
this.numFailures = 0;
|
||||||
|
this.lastFailure = 0;
|
||||||
|
this.lastIPFailure = null;
|
||||||
|
this.failedLoginNotBefore = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,7 +51,7 @@ public class UsernameLoginFailureAdapter implements UsernameLoginFailureModel {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void clearFailures() {
|
public void clearFailures() {
|
||||||
entity.setNumFailures(0);
|
entity.clearFailures();
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -62,4 +62,10 @@ public class LoginFailureEntity implements Serializable {
|
||||||
this.lastIPFailure = lastIPFailure;
|
this.lastIPFailure = lastIPFailure;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void clearFailures() {
|
||||||
|
this.failedLoginNotBefore = 0;
|
||||||
|
this.numFailures = 0;
|
||||||
|
this.lastFailure = 0;
|
||||||
|
this.lastIPFailure = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,7 +43,7 @@ public class UsernameLoginFailureAdapter implements UsernameLoginFailureModel
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void clearFailures() {
|
public void clearFailures() {
|
||||||
user.setNumFailures(0);
|
user.clearFailures();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -91,6 +91,13 @@ public class UsernameLoginFailureEntity {
|
||||||
this.realmId = realmId;
|
this.realmId = realmId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void clearFailures() {
|
||||||
|
setFailedLoginNotBefore(0);
|
||||||
|
setLastFailure(0);
|
||||||
|
setLastIPFailure(null);
|
||||||
|
setNumFailures(0);
|
||||||
|
}
|
||||||
|
|
||||||
public static class Key implements Serializable {
|
public static class Key implements Serializable {
|
||||||
|
|
||||||
private String realmId;
|
private String realmId;
|
||||||
|
|
|
@ -45,7 +45,7 @@ public class UsernameLoginFailureAdapter implements UsernameLoginFailureModel {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void clearFailures() {
|
public void clearFailures() {
|
||||||
entity.getNumFailures().set(0);
|
entity.clearFailures();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -62,4 +62,11 @@ public class UsernameLoginFailureEntity {
|
||||||
this.lastIpFailure = lastIpFailure;
|
this.lastIpFailure = lastIpFailure;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void clearFailures() {
|
||||||
|
this.failedLoginNotBefore = new AtomicInteger();
|
||||||
|
this.lastFailure = new AtomicLong();
|
||||||
|
this.lastIpFailure = new AtomicReference<String>();
|
||||||
|
this.numFailures = new AtomicInteger();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,7 +50,7 @@ public class UsernameLoginFailureAdapter extends AbstractMongoAdapter<MongoUser
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void clearFailures() {
|
public void clearFailures() {
|
||||||
user.setNumFailures(0);
|
user.clearFailures();
|
||||||
updateMongoEntity();
|
updateMongoEntity();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -68,6 +68,8 @@ import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import org.keycloak.models.UsernameLoginFailureModel;
|
||||||
|
import org.keycloak.services.managers.BruteForceProtector;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base resource for managing users
|
* Base resource for managing users
|
||||||
|
@ -81,9 +83,9 @@ public class UsersResource {
|
||||||
protected RealmModel realm;
|
protected RealmModel realm;
|
||||||
|
|
||||||
private RealmAuth auth;
|
private RealmAuth auth;
|
||||||
|
|
||||||
private AdminEventBuilder adminEvent;
|
private AdminEventBuilder adminEvent;
|
||||||
|
|
||||||
@Context
|
@Context
|
||||||
protected ClientConnection clientConnection;
|
protected ClientConnection clientConnection;
|
||||||
|
|
||||||
|
@ -96,6 +98,9 @@ public class UsersResource {
|
||||||
@Context
|
@Context
|
||||||
protected HttpHeaders headers;
|
protected HttpHeaders headers;
|
||||||
|
|
||||||
|
@Context
|
||||||
|
protected BruteForceProtector protector;
|
||||||
|
|
||||||
public UsersResource(RealmModel realm, RealmAuth auth, TokenManager tokenManager, AdminEventBuilder adminEvent) {
|
public UsersResource(RealmModel realm, RealmAuth auth, TokenManager tokenManager, AdminEventBuilder adminEvent) {
|
||||||
this.auth = auth;
|
this.auth = auth;
|
||||||
this.realm = realm;
|
this.realm = realm;
|
||||||
|
@ -131,6 +136,13 @@ public class UsersResource {
|
||||||
attrsToRemove = Collections.emptySet();
|
attrsToRemove = Collections.emptySet();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (rep.isEnabled()) {
|
||||||
|
UsernameLoginFailureModel failureModel = session.sessions().getUserLoginFailure(realm, rep.getUsername());
|
||||||
|
if (failureModel != null) {
|
||||||
|
failureModel.clearFailures();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
updateUserFromRep(user, rep, attrsToRemove);
|
updateUserFromRep(user, rep, attrsToRemove);
|
||||||
adminEvent.operation(OperationType.UPDATE).resourcePath(uriInfo).representation(rep).success();
|
adminEvent.operation(OperationType.UPDATE).resourcePath(uriInfo).representation(rep).success();
|
||||||
|
|
||||||
|
@ -169,13 +181,13 @@ public class UsersResource {
|
||||||
UserModel user = session.users().addUser(realm, rep.getUsername());
|
UserModel user = session.users().addUser(realm, rep.getUsername());
|
||||||
Set<String> emptySet = Collections.emptySet();
|
Set<String> emptySet = Collections.emptySet();
|
||||||
updateUserFromRep(user, rep, emptySet);
|
updateUserFromRep(user, rep, emptySet);
|
||||||
|
|
||||||
adminEvent.operation(OperationType.CREATE).resourcePath(uriInfo, user.getId()).representation(rep).success();
|
adminEvent.operation(OperationType.CREATE).resourcePath(uriInfo, user.getId()).representation(rep).success();
|
||||||
|
|
||||||
if (session.getTransaction().isActive()) {
|
if (session.getTransaction().isActive()) {
|
||||||
session.getTransaction().commit();
|
session.getTransaction().commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
return Response.created(uriInfo.getAbsolutePathBuilder().path(user.getId()).build()).build();
|
return Response.created(uriInfo.getAbsolutePathBuilder().path(user.getId()).build()).build();
|
||||||
} catch (ModelDuplicateException e) {
|
} catch (ModelDuplicateException e) {
|
||||||
if (session.getTransaction().isActive()) {
|
if (session.getTransaction().isActive()) {
|
||||||
|
@ -237,7 +249,7 @@ public class UsersResource {
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
throw new NotFoundException("User not found");
|
throw new NotFoundException("User not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
UserRepresentation rep = ModelToRepresentation.toRepresentation(user);
|
UserRepresentation rep = ModelToRepresentation.toRepresentation(user);
|
||||||
|
|
||||||
if (realm.isIdentityFederationEnabled()) {
|
if (realm.isIdentityFederationEnabled()) {
|
||||||
|
@ -251,6 +263,10 @@ public class UsersResource {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ((protector != null) && protector.isTemporarilyDisabled(session, realm, rep.getUsername())) {
|
||||||
|
rep.setEnabled(false);
|
||||||
|
}
|
||||||
|
|
||||||
return rep;
|
return rep;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -689,7 +705,7 @@ public class UsersResource {
|
||||||
adminEvent.operation(OperationType.DELETE).resourcePath(uriInfo, role.getId()).representation(roles).success();
|
adminEvent.operation(OperationType.DELETE).resourcePath(uriInfo, role.getId()).representation(roles).success();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Path("{id}/role-mappings/clients/{client}")
|
@Path("{id}/role-mappings/clients/{client}")
|
||||||
|
@ -703,7 +719,7 @@ public class UsersResource {
|
||||||
if (client == null) {
|
if (client == null) {
|
||||||
throw new NotFoundException("Client not found");
|
throw new NotFoundException("Client not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
return new UserClientRoleMappingsResource(uriInfo, realm, auth, user, clientModel, adminEvent);
|
return new UserClientRoleMappingsResource(uriInfo, realm, auth, user, clientModel, adminEvent);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -737,7 +753,7 @@ public class UsersResource {
|
||||||
throw new BadRequestException("Can't reset password as account is read only");
|
throw new BadRequestException("Can't reset password as account is read only");
|
||||||
}
|
}
|
||||||
if (pass.isTemporary()) user.addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD);
|
if (pass.isTemporary()) user.addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD);
|
||||||
|
|
||||||
adminEvent.operation(OperationType.ACTION).resourcePath(uriInfo).success();
|
adminEvent.operation(OperationType.ACTION).resourcePath(uriInfo).success();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue