KEYCLOAK-1083: Provide a way for admin to unlock user account

This commit is contained in:
Stan Silvert 2015-06-11 15:32:03 -04:00
parent 0095968a73
commit d6e64a2c5e
9 changed files with 56 additions and 13 deletions

View file

@ -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;
}
} }

View file

@ -51,7 +51,7 @@ public class UsernameLoginFailureAdapter implements UsernameLoginFailureModel {
@Override @Override
public void clearFailures() { public void clearFailures() {
entity.setNumFailures(0); entity.clearFailures();
update(); update();
} }

View file

@ -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;
}
} }

View file

@ -43,7 +43,7 @@ public class UsernameLoginFailureAdapter implements UsernameLoginFailureModel
@Override @Override
public void clearFailures() { public void clearFailures() {
user.setNumFailures(0); user.clearFailures();
} }
@Override @Override

View file

@ -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;

View file

@ -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

View file

@ -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();
}
} }

View file

@ -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();
} }

View file

@ -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();
} }