1st phase auth/required action spi
This commit is contained in:
parent
e4204a56f5
commit
338300df32
41 changed files with 1574 additions and 58 deletions
|
@ -0,0 +1,49 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd">
|
||||
<changeSet author="bburke@redhat.com" id="1.3.0.Beta1">
|
||||
<delete tableName="CLIENT_SESSION_ROLE"/>
|
||||
<delete tableName="CLIENT_SESSION_NOTE"/>
|
||||
<delete tableName="CLIENT_SESSION"/>
|
||||
<delete tableName="USER_SESSION_NOTE"/>
|
||||
<delete tableName="USER_SESSION"/>
|
||||
<createTable tableName="CLIENT_SESSION_AUTH_STATUS">
|
||||
<column name="AUTHENTICATOR" type="VARCHAR(32)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="STATUS" type="INT"/>
|
||||
<column name="CLIENT_SESSION" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</createTable>
|
||||
<addColumn tableName="CLIENT_SESSION">
|
||||
<column name="AUTH_USER_ID" type="VARCHAR(32)"/>
|
||||
</addColumn>
|
||||
<addColumn tableName="USER_REQUIRED_ACTION">
|
||||
<column name="REQUIRED_ACTION" type="VARCHAR(32)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</addColumn>
|
||||
<!-- VERIFY_EMAIL, UPDATE_PROFILE, CONFIGURE_TOTP, UPDATE_PASSWORD -->
|
||||
<update tableName="USER_REQUIRED_ACTION">
|
||||
<column name="REQUIRED_ACTION" value="VERIFY_EMAIL"/>
|
||||
<where>ACTION = 0</where>
|
||||
</update>
|
||||
<update tableName="USER_REQUIRED_ACTION">
|
||||
<column name="REQUIRED_ACTION" value="UPDATE_PROFILE"/>
|
||||
<where>ACTION = 1</where>
|
||||
</update>
|
||||
<update tableName="USER_REQUIRED_ACTION">
|
||||
<column name="REQUIRED_ACTION" value="CONFIGURE_TOTP"/>
|
||||
<where>ACTION = 2</where>
|
||||
</update>
|
||||
<update tableName="USER_REQUIRED_ACTION">
|
||||
<column name="REQUIRED_ACTION" value="UPDATE_PASSWORD"/>
|
||||
<where>ACTION = 3</where>
|
||||
</update>
|
||||
<dropPrimaryKey constraintName="CONSTRAINT_2" tableName="USER_REQUIRED_ACTION"/>
|
||||
<dropColumn tableName="USER_REQUIRED_ACTION" columnName="ACTION"/>
|
||||
<addPrimaryKey columnNames="REQUIRED_ACTION, USER_ID" constraintName="CONSTRAINT_REQUIRED_ACTION" tableName="USER_REQUIRED_ACTION"/>
|
||||
<addPrimaryKey columnNames="CLIENT_SESSION, AUTHENTICATOR" constraintName="CONSTRAINT_AUTH_STATUS_PK" tableName="CLIENT_SESSION_AUTH_STATUS"/>
|
||||
<addForeignKeyConstraint baseColumnNames="CLIENT_SESSION" baseTableName="CLIENT_SESSION_AUTH_STATUS" constraintName="AUTH_STATUS_CONSTRAINT" referencedColumnNames="ID" referencedTableName="CLIENT_SESSION"/>
|
||||
</changeSet>
|
||||
</databaseChangeLog>
|
|
@ -5,4 +5,5 @@
|
|||
<include file="META-INF/jpa-changelog-1.1.0.Final.xml"/>
|
||||
<include file="META-INF/jpa-changelog-1.2.0.Beta1.xml"/>
|
||||
<include file="META-INF/jpa-changelog-1.2.0.CR1.xml"/>
|
||||
<include file="META-INF/jpa-changelog-1.3.0.Beta1.xml"/>
|
||||
</databaseChangeLog>
|
||||
|
|
|
@ -12,7 +12,7 @@ public interface JpaUpdaterProvider extends Provider {
|
|||
|
||||
public String FIRST_VERSION = "1.0.0.Final";
|
||||
|
||||
public String LAST_VERSION = "1.2.0.RC1";
|
||||
public String LAST_VERSION = "1.3.0.Beta1";
|
||||
|
||||
public String getCurrentVersionSql();
|
||||
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
<!-- JpaUserSessionProvider -->
|
||||
<class>org.keycloak.models.sessions.jpa.entities.ClientSessionEntity</class>
|
||||
<class>org.keycloak.models.sessions.jpa.entities.ClientSessionRoleEntity</class>
|
||||
<class>org.keycloak.models.sessions.jpa.entities.ClientSessionAuthStatusEntity</class>
|
||||
<class>org.keycloak.models.sessions.jpa.entities.ClientSessionProtocolMapperEntity</class>
|
||||
<class>org.keycloak.models.sessions.jpa.entities.ClientSessionNoteEntity</class>
|
||||
<class>org.keycloak.models.sessions.jpa.entities.UserSessionNoteEntity</class>
|
||||
|
|
127
model/api/src/main/java/org/keycloak/models/AuthenticatorModel.java
Executable file
127
model/api/src/main/java/org/keycloak/models/AuthenticatorModel.java
Executable file
|
@ -0,0 +1,127 @@
|
|||
package org.keycloak.models;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class AuthenticatorModel {
|
||||
|
||||
public enum Requirement {
|
||||
REQUIRED,
|
||||
OPTIONAL,
|
||||
ALTERNATIVE
|
||||
}
|
||||
|
||||
private String id;
|
||||
private String alias;
|
||||
private String providerId;
|
||||
private boolean masterAuthenticator;
|
||||
private boolean formBased;
|
||||
private String inputPage;
|
||||
private String actionUrl;
|
||||
private String setupUrl;
|
||||
private Requirement requirement;
|
||||
private boolean userSetupAllowed;
|
||||
private int priority;
|
||||
private Map<String, String> config = new HashMap<String, String>();
|
||||
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getAlias() {
|
||||
return alias;
|
||||
}
|
||||
|
||||
public void setAlias(String alias) {
|
||||
this.alias = alias;
|
||||
}
|
||||
|
||||
public String getProviderId() {
|
||||
return providerId;
|
||||
}
|
||||
|
||||
public void setProviderId(String providerId) {
|
||||
this.providerId = providerId;
|
||||
}
|
||||
|
||||
public boolean isFormBased() {
|
||||
return formBased;
|
||||
}
|
||||
|
||||
public void setFormBased(boolean formBased) {
|
||||
this.formBased = formBased;
|
||||
}
|
||||
|
||||
public String getInputPage() {
|
||||
return inputPage;
|
||||
}
|
||||
|
||||
public void setInputPage(String inputPage) {
|
||||
this.inputPage = inputPage;
|
||||
}
|
||||
|
||||
public String getActionUrl() {
|
||||
return actionUrl;
|
||||
}
|
||||
|
||||
public void setActionUrl(String actionUrl) {
|
||||
this.actionUrl = actionUrl;
|
||||
}
|
||||
|
||||
public String getSetupUrl() {
|
||||
return setupUrl;
|
||||
}
|
||||
|
||||
public void setSetupUrl(String setupUrl) {
|
||||
this.setupUrl = setupUrl;
|
||||
}
|
||||
|
||||
public Requirement getRequirement() {
|
||||
return requirement;
|
||||
}
|
||||
|
||||
public void setRequirement(Requirement requirement) {
|
||||
this.requirement = requirement;
|
||||
}
|
||||
|
||||
public int getPriority() {
|
||||
return priority;
|
||||
}
|
||||
|
||||
public void setPriority(int priority) {
|
||||
this.priority = priority;
|
||||
}
|
||||
|
||||
public boolean isUserSetupAllowed() {
|
||||
return userSetupAllowed;
|
||||
}
|
||||
|
||||
public void setUserSetupAllowed(boolean userSetupAllowed) {
|
||||
this.userSetupAllowed = userSetupAllowed;
|
||||
}
|
||||
|
||||
public boolean isMasterAuthenticator() {
|
||||
return masterAuthenticator;
|
||||
}
|
||||
|
||||
public void setMasterAuthenticator(boolean masterAuthenticator) {
|
||||
this.masterAuthenticator = masterAuthenticator;
|
||||
}
|
||||
|
||||
public Map<String, String> getConfig() {
|
||||
return config;
|
||||
}
|
||||
|
||||
public void setConfig(Map<String, String> config) {
|
||||
this.config = config;
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
package org.keycloak.models;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
|
@ -31,6 +32,14 @@ public interface ClientSessionModel {
|
|||
public Set<String> getProtocolMappers();
|
||||
public void setProtocolMappers(Set<String> protocolMappers);
|
||||
|
||||
public Map<String, UserSessionModel.AuthenticatorStatus> getAuthenticators();
|
||||
public void setAuthenticatorStatus(String authenticator, UserSessionModel.AuthenticatorStatus status);
|
||||
public void setAuthenticatorStatus(Map<String, UserSessionModel.AuthenticatorStatus> status);
|
||||
public UserModel getAuthenticatedUser();
|
||||
public void setAuthenticatedUser(UserModel user);
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Authentication request type, i.e. OAUTH, SAML 2.0, SAML 1.1, etc.
|
||||
*
|
||||
|
|
|
@ -35,8 +35,12 @@ public interface UserModel {
|
|||
|
||||
Map<String, String> getAttributes();
|
||||
|
||||
Set<RequiredAction> getRequiredActions();
|
||||
|
||||
Set<String> getRequiredActions();
|
||||
|
||||
void addRequiredAction(String action);
|
||||
|
||||
void removeRequiredAction(String action);
|
||||
|
||||
void addRequiredAction(RequiredAction action);
|
||||
|
||||
void removeRequiredAction(RequiredAction action);
|
||||
|
@ -65,6 +69,14 @@ public interface UserModel {
|
|||
|
||||
void updateCredentialDirectly(UserCredentialValueModel cred);
|
||||
|
||||
/**
|
||||
* Is the use configured to use this credential type
|
||||
*
|
||||
* @param type
|
||||
* @return
|
||||
*/
|
||||
boolean configuredForCredentialType(String type);
|
||||
|
||||
Set<RoleModel> getRealmRoleMappings();
|
||||
Set<RoleModel> getClientRoleMappings(ClientModel app);
|
||||
boolean hasRole(RoleModel role);
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package org.keycloak.models;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
|
@ -36,6 +37,13 @@ public interface UserSessionModel {
|
|||
|
||||
List<ClientSessionModel> getClientSessions();
|
||||
|
||||
public static enum AuthenticatorStatus {
|
||||
SUCCESS,
|
||||
SETUP_REQUIRED,
|
||||
ATTEMPTED,
|
||||
SKIPPED
|
||||
}
|
||||
|
||||
public String getNote(String name);
|
||||
public void setNote(String name, String value);
|
||||
public void removeNote(String name);
|
||||
|
|
|
@ -24,7 +24,7 @@ public class UserEntity extends AbstractIdentifiableEntity {
|
|||
private List<String> roleIds;
|
||||
|
||||
private Map<String, String> attributes;
|
||||
private List<UserModel.RequiredAction> requiredActions;
|
||||
private List<String> requiredActions;
|
||||
private List<CredentialEntity> credentials = new ArrayList<CredentialEntity>();
|
||||
private List<FederatedIdentityEntity> federatedIdentities;
|
||||
private String federationLink;
|
||||
|
@ -109,11 +109,11 @@ public class UserEntity extends AbstractIdentifiableEntity {
|
|||
this.attributes = attributes;
|
||||
}
|
||||
|
||||
public List<UserModel.RequiredAction> getRequiredActions() {
|
||||
public List<String> getRequiredActions() {
|
||||
return requiredActions;
|
||||
}
|
||||
|
||||
public void setRequiredActions(List<UserModel.RequiredAction> requiredActions) {
|
||||
public void setRequiredActions(List<String> requiredActions) {
|
||||
this.requiredActions = requiredActions;
|
||||
}
|
||||
|
||||
|
|
|
@ -55,8 +55,8 @@ public class ModelToRepresentation {
|
|||
rep.setFederationLink(user.getFederationLink());
|
||||
|
||||
List<String> reqActions = new ArrayList<String>();
|
||||
for (UserModel.RequiredAction ra : user.getRequiredActions()){
|
||||
reqActions.add(ra.name());
|
||||
for (String ra : user.getRequiredActions()){
|
||||
reqActions.add(ra);
|
||||
}
|
||||
|
||||
rep.setRequiredActions(reqActions);
|
||||
|
|
|
@ -73,10 +73,25 @@ public class UserModelDelegate implements UserModel {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Set<RequiredAction> getRequiredActions() {
|
||||
public Set<String> getRequiredActions() {
|
||||
return delegate.getRequiredActions();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addRequiredAction(String action) {
|
||||
delegate.addRequiredAction(action);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeRequiredAction(String action) {
|
||||
delegate.removeRequiredAction(action);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean configuredForCredentialType(String type) {
|
||||
return delegate.configuredForCredentialType(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addRequiredAction(RequiredAction action) {
|
||||
delegate.addRequiredAction(action);
|
||||
|
@ -211,4 +226,5 @@ public class UserModelDelegate implements UserModel {
|
|||
public boolean revokeConsentForClient(String clientId) {
|
||||
return delegate.revokeConsentForClient(clientId);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -183,28 +183,53 @@ public class UserAdapter implements UserModel, Comparable {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Set<RequiredAction> getRequiredActions() {
|
||||
List<RequiredAction> requiredActions = user.getRequiredActions();
|
||||
if (requiredActions == null) requiredActions = new ArrayList<RequiredAction>();
|
||||
public Set<String> getRequiredActions() {
|
||||
List<String> requiredActions = user.getRequiredActions();
|
||||
if (requiredActions == null) requiredActions = new ArrayList<String>();
|
||||
return new HashSet(requiredActions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addRequiredAction(RequiredAction action) {
|
||||
List<RequiredAction> requiredActions = user.getRequiredActions();
|
||||
if (requiredActions == null) requiredActions = new ArrayList<RequiredAction>();
|
||||
if (!requiredActions.contains(action)) requiredActions.add(action);
|
||||
String actionName = action.name();
|
||||
addRequiredAction(actionName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addRequiredAction(String actionName) {
|
||||
List<String> requiredActions = user.getRequiredActions();
|
||||
if (requiredActions == null) requiredActions = new ArrayList<>();
|
||||
if (!requiredActions.contains(actionName)) {
|
||||
requiredActions.add(actionName);
|
||||
}
|
||||
user.setRequiredActions(requiredActions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeRequiredAction(RequiredAction action) {
|
||||
List<RequiredAction> requiredActions = user.getRequiredActions();
|
||||
String actionName = action.name();
|
||||
removeRequiredAction(actionName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeRequiredAction(String actionName) {
|
||||
List<String> requiredActions = user.getRequiredActions();
|
||||
if (requiredActions == null) return;
|
||||
requiredActions.remove(action);
|
||||
requiredActions.remove(actionName);
|
||||
user.setRequiredActions(requiredActions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean configuredForCredentialType(String type) {
|
||||
List<UserCredentialValueModel> creds = getCredentialsDirectly();
|
||||
for (UserCredentialValueModel cred : creds) {
|
||||
if (cred.getType().equals(type)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isTotp() {
|
||||
return user.isTotp();
|
||||
|
|
|
@ -102,7 +102,7 @@ public class UserAdapter implements UserModel {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Set<RequiredAction> getRequiredActions() {
|
||||
public Set<String> getRequiredActions() {
|
||||
if (updated != null) return updated.getRequiredActions();
|
||||
return cached.getRequiredActions();
|
||||
}
|
||||
|
@ -119,6 +119,27 @@ public class UserAdapter implements UserModel {
|
|||
updated.removeRequiredAction(action);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addRequiredAction(String action) {
|
||||
getDelegateForUpdate();
|
||||
updated.addRequiredAction(action);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeRequiredAction(String action) {
|
||||
getDelegateForUpdate();
|
||||
updated.removeRequiredAction(action);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean configuredForCredentialType(String type) {
|
||||
List<UserCredentialValueModel> creds = getCredentialsDirectly();
|
||||
for (UserCredentialValueModel cred : creds) {
|
||||
if (cred.getType().equals(type)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFirstName() {
|
||||
if (updated != null) return updated.getFirstName();
|
||||
|
|
|
@ -28,8 +28,8 @@ public class CachedUser {
|
|||
private boolean enabled;
|
||||
private boolean totp;
|
||||
private String federationLink;
|
||||
private Map<String, String> attributes = new HashMap<String, String>();
|
||||
private Set<UserModel.RequiredAction> requiredActions = new HashSet<UserModel.RequiredAction>();
|
||||
private Map<String, String> attributes = new HashMap<>();
|
||||
private Set<String> requiredActions = new HashSet<>();
|
||||
private Set<String> roleMappings = new HashSet<String>();
|
||||
|
||||
|
||||
|
@ -96,7 +96,7 @@ public class CachedUser {
|
|||
return attributes;
|
||||
}
|
||||
|
||||
public Set<UserModel.RequiredAction> getRequiredActions() {
|
||||
public Set<String> getRequiredActions() {
|
||||
return requiredActions;
|
||||
}
|
||||
|
||||
|
|
|
@ -139,8 +139,8 @@ public class UserAdapter implements UserModel {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Set<RequiredAction> getRequiredActions() {
|
||||
Set<RequiredAction> result = new HashSet<RequiredAction>();
|
||||
public Set<String> getRequiredActions() {
|
||||
Set<String> result = new HashSet<>();
|
||||
for (UserRequiredActionEntity attr : user.getRequiredActions()) {
|
||||
result.add(attr.getAction());
|
||||
}
|
||||
|
@ -149,13 +149,19 @@ public class UserAdapter implements UserModel {
|
|||
|
||||
@Override
|
||||
public void addRequiredAction(RequiredAction action) {
|
||||
String actionName = action.name();
|
||||
addRequiredAction(actionName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addRequiredAction(String actionName) {
|
||||
for (UserRequiredActionEntity attr : user.getRequiredActions()) {
|
||||
if (attr.getAction().equals(action)) {
|
||||
if (attr.getAction().equals(actionName)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
UserRequiredActionEntity attr = new UserRequiredActionEntity();
|
||||
attr.setAction(action);
|
||||
attr.setAction(actionName);
|
||||
attr.setUser(user);
|
||||
em.persist(attr);
|
||||
user.getRequiredActions().add(attr);
|
||||
|
@ -163,16 +169,31 @@ public class UserAdapter implements UserModel {
|
|||
|
||||
@Override
|
||||
public void removeRequiredAction(RequiredAction action) {
|
||||
String actionName = action.name();
|
||||
removeRequiredAction(actionName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeRequiredAction(String actionName) {
|
||||
Iterator<UserRequiredActionEntity> it = user.getRequiredActions().iterator();
|
||||
while (it.hasNext()) {
|
||||
UserRequiredActionEntity attr = it.next();
|
||||
if (attr.getAction().equals(action)) {
|
||||
if (attr.getAction().equals(actionName)) {
|
||||
it.remove();
|
||||
em.remove(attr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean configuredForCredentialType(String type) {
|
||||
List<UserCredentialValueModel> creds = getCredentialsDirectly();
|
||||
for (UserCredentialValueModel cred : creds) {
|
||||
if (cred.getType().equals(type)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFirstName() {
|
||||
return user.getFirstName();
|
||||
|
|
|
@ -33,14 +33,14 @@ public class UserRequiredActionEntity {
|
|||
protected UserEntity user;
|
||||
|
||||
@Id
|
||||
@Column(name="ACTION")
|
||||
protected UserModel.RequiredAction action;
|
||||
@Column(name="REQUIRED_ACTION")
|
||||
protected String action;
|
||||
|
||||
public UserModel.RequiredAction getAction() {
|
||||
public String getAction() {
|
||||
return action;
|
||||
}
|
||||
|
||||
public void setAction(UserModel.RequiredAction action) {
|
||||
public void setAction(String action) {
|
||||
this.action = action;
|
||||
}
|
||||
|
||||
|
@ -56,12 +56,12 @@ public class UserRequiredActionEntity {
|
|||
|
||||
protected UserEntity user;
|
||||
|
||||
protected UserModel.RequiredAction action;
|
||||
protected String action;
|
||||
|
||||
public Key() {
|
||||
}
|
||||
|
||||
public Key(UserEntity user, UserModel.RequiredAction action) {
|
||||
public Key(UserEntity user, String action) {
|
||||
this.user = user;
|
||||
this.action = action;
|
||||
}
|
||||
|
@ -70,7 +70,7 @@ public class UserRequiredActionEntity {
|
|||
return user;
|
||||
}
|
||||
|
||||
public UserModel.RequiredAction getAction() {
|
||||
public String getAction() {
|
||||
return action;
|
||||
}
|
||||
|
||||
|
|
|
@ -159,8 +159,8 @@ public class UserAdapter extends AbstractMongoAdapter<MongoUserEntity> implement
|
|||
|
||||
|
||||
@Override
|
||||
public Set<RequiredAction> getRequiredActions() {
|
||||
Set<RequiredAction> result = new HashSet<RequiredAction>();
|
||||
public Set<String> getRequiredActions() {
|
||||
Set<String> result = new HashSet<String>();
|
||||
if (user.getRequiredActions() != null) {
|
||||
result.addAll(user.getRequiredActions());
|
||||
}
|
||||
|
@ -169,12 +169,24 @@ public class UserAdapter extends AbstractMongoAdapter<MongoUserEntity> implement
|
|||
|
||||
@Override
|
||||
public void addRequiredAction(RequiredAction action) {
|
||||
getMongoStore().pushItemToList(user, "requiredActions", action, true, invocationContext);
|
||||
String actionName = action.name();
|
||||
addRequiredAction(actionName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addRequiredAction(String actionName) {
|
||||
getMongoStore().pushItemToList(user, "requiredActions", actionName, true, invocationContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeRequiredAction(RequiredAction action) {
|
||||
getMongoStore().pullItemFromList(user, "requiredActions", action, invocationContext);
|
||||
String actionName = action.name();
|
||||
removeRequiredAction(actionName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeRequiredAction(String actionName) {
|
||||
getMongoStore().pullItemFromList(user, "requiredActions", actionName, invocationContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -320,6 +332,16 @@ public class UserAdapter extends AbstractMongoAdapter<MongoUserEntity> implement
|
|||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean configuredForCredentialType(String type) {
|
||||
List<UserCredentialValueModel> creds = getCredentialsDirectly();
|
||||
for (UserCredentialValueModel cred : creds) {
|
||||
if (cred.getType().equals(type)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void updateCredentialDirectly(UserCredentialValueModel credModel) {
|
||||
CredentialEntity credentialEntity = getCredentialEntity(user, credModel.getType());
|
||||
|
|
27
model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/ClientSessionAdapter.java
Normal file → Executable file
27
model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/ClientSessionAdapter.java
Normal file → Executable file
|
@ -5,11 +5,13 @@ import org.keycloak.models.ClientModel;
|
|||
import org.keycloak.models.ClientSessionModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
import org.keycloak.models.sessions.infinispan.entities.ClientSessionEntity;
|
||||
import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
|
@ -165,5 +167,30 @@ public class ClientSessionAdapter implements ClientSessionModel {
|
|||
void update() {
|
||||
provider.getTx().replace(cache, entity.getId(), entity);
|
||||
}
|
||||
@Override
|
||||
public Map<String, UserSessionModel.AuthenticatorStatus> getAuthenticators() {
|
||||
return entity.getAuthenticatorStatus();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAuthenticatorStatus(String authenticator, UserSessionModel.AuthenticatorStatus status) {
|
||||
entity.getAuthenticatorStatus().put(authenticator, status);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAuthenticatorStatus(Map<String, UserSessionModel.AuthenticatorStatus> status) {
|
||||
entity.setAuthenticatorStatus(status);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserModel getAuthenticatedUser() {
|
||||
return session.users().getUserById(entity.getAuthUserId(), realm); }
|
||||
|
||||
@Override
|
||||
public void setAuthenticatedUser(UserModel user) {
|
||||
entity.setAuthUserId(user.getId());
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
package org.keycloak.models.sessions.infinispan.entities;
|
||||
|
||||
import org.keycloak.models.ClientSessionModel;
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
|
@ -27,6 +29,8 @@ public class ClientSessionEntity extends SessionEntity {
|
|||
private Set<String> roles;
|
||||
private Set<String> protocolMappers;
|
||||
private Map<String, String> notes;
|
||||
private Map<String, UserSessionModel.AuthenticatorStatus> authenticatorStatus = new HashMap<>();
|
||||
private String authUserId;
|
||||
|
||||
public String getClient() {
|
||||
return client;
|
||||
|
@ -107,4 +111,20 @@ public class ClientSessionEntity extends SessionEntity {
|
|||
public void setNotes(Map<String, String> notes) {
|
||||
this.notes = notes;
|
||||
}
|
||||
|
||||
public Map<String, UserSessionModel.AuthenticatorStatus> getAuthenticatorStatus() {
|
||||
return authenticatorStatus;
|
||||
}
|
||||
|
||||
public void setAuthenticatorStatus(Map<String, UserSessionModel.AuthenticatorStatus> authenticatorStatus) {
|
||||
this.authenticatorStatus = authenticatorStatus;
|
||||
}
|
||||
|
||||
public String getAuthUserId() {
|
||||
return authUserId;
|
||||
}
|
||||
|
||||
public void setAuthUserId(String authUserId) {
|
||||
this.authUserId = authUserId;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import org.keycloak.models.ClientModel;
|
|||
import org.keycloak.models.ClientSessionModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
import org.keycloak.models.sessions.jpa.entities.ClientSessionEntity;
|
||||
import org.keycloak.models.sessions.jpa.entities.ClientSessionNoteEntity;
|
||||
|
@ -14,6 +15,7 @@ import org.keycloak.models.sessions.jpa.entities.UserSessionEntity;
|
|||
import javax.persistence.EntityManager;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
|
@ -210,4 +212,29 @@ public class ClientSessionAdapter implements ClientSessionModel {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, UserSessionModel.AuthenticatorStatus> getAuthenticators() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAuthenticatorStatus(String authenticator, UserSessionModel.AuthenticatorStatus status) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAuthenticatorStatus(Map<String, UserSessionModel.AuthenticatorStatus> status) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserModel getAuthenticatedUser() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAuthenticatedUser(UserModel user) {
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,111 @@
|
|||
package org.keycloak.models.sessions.jpa.entities;
|
||||
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.FetchType;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.IdClass;
|
||||
import javax.persistence.JoinColumn;
|
||||
import javax.persistence.ManyToOne;
|
||||
import javax.persistence.NamedQueries;
|
||||
import javax.persistence.NamedQuery;
|
||||
import javax.persistence.Table;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
@NamedQueries({
|
||||
@NamedQuery(name = "removeClientSessionAuthStatusByUser", query="delete from ClientSessionAuthStatusEntity r where r.clientSession IN (select c from ClientSessionEntity c where c.session IN (select s from UserSessionEntity s where s.realmId = :realmId and s.userId = :userId))"),
|
||||
@NamedQuery(name = "removeClientSessionAuthStatusByClient", query="delete from ClientSessionAuthStatusEntity r where r.clientSession IN (select c from ClientSessionEntity c where c.clientId = :clientId and c.realmId = :realmId)"),
|
||||
@NamedQuery(name = "removeClientSessionAuthStatusByRealm", query="delete from ClientSessionAuthStatusEntity r where r.clientSession IN (select c from ClientSessionEntity c where c.realmId = :realmId)"),
|
||||
@NamedQuery(name = "removeClientSessionAuthStatusByExpired", query = "delete from ClientSessionAuthStatusEntity r where r.clientSession IN (select c from ClientSessionEntity c where c.session IN (select s from UserSessionEntity s where s.realmId = :realmId and (s.started < :maxTime or s.lastSessionRefresh < :idleTime)))"),
|
||||
@NamedQuery(name = "removeDetachedClientSessionAuthStatusByExpired", query = "delete from ClientSessionAuthStatusEntity r where r.clientSession IN (select c from ClientSessionEntity c where c.session IS NULL and c.realmId = :realmId and c.timestamp < :maxTime )")
|
||||
})
|
||||
@Table(name="CLIENT_SESSION_AUTH_STATUS")
|
||||
@Entity
|
||||
@IdClass(ClientSessionAuthStatusEntity.Key.class)
|
||||
public class ClientSessionAuthStatusEntity {
|
||||
|
||||
@Id
|
||||
@ManyToOne(fetch= FetchType.LAZY)
|
||||
@JoinColumn(name = "CLIENT_SESSION")
|
||||
protected ClientSessionEntity clientSession;
|
||||
|
||||
@Id
|
||||
@Column(name = "AUTHENTICATOR")
|
||||
protected String authenticator;
|
||||
@Column(name = "STATUS")
|
||||
protected UserSessionModel.AuthenticatorStatus status;
|
||||
|
||||
public String getAuthenticator() {
|
||||
return authenticator;
|
||||
}
|
||||
|
||||
public void setAuthenticator(String authenticator) {
|
||||
this.authenticator = authenticator;
|
||||
}
|
||||
|
||||
public UserSessionModel.AuthenticatorStatus getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(UserSessionModel.AuthenticatorStatus status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public ClientSessionEntity getClientSession() {
|
||||
return clientSession;
|
||||
}
|
||||
|
||||
public void setClientSession(ClientSessionEntity clientSession) {
|
||||
this.clientSession = clientSession;
|
||||
}
|
||||
|
||||
public static class Key implements Serializable {
|
||||
|
||||
protected ClientSessionEntity clientSession;
|
||||
|
||||
protected String authenticator;
|
||||
|
||||
public Key() {
|
||||
}
|
||||
|
||||
public Key(ClientSessionEntity clientSession, String authenticator) {
|
||||
this.clientSession = clientSession;
|
||||
this.authenticator = authenticator;
|
||||
}
|
||||
|
||||
public ClientSessionEntity getClientSession() {
|
||||
return clientSession;
|
||||
}
|
||||
|
||||
public String getAuthenticator() {
|
||||
return authenticator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
Key key = (Key) o;
|
||||
|
||||
if (authenticator != null ? !authenticator.equals(key.authenticator) : key.authenticator != null) return false;
|
||||
if (clientSession != null ? !clientSession.getId().equals(key.clientSession != null ? key.clientSession.getId() : null) : key.clientSession != null) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = clientSession != null ? clientSession.getId().hashCode() : 0;
|
||||
result = 31 * result + (authenticator != null ? authenticator.hashCode() : 0);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -57,6 +57,9 @@ public class ClientSessionEntity {
|
|||
@Column(name="ACTION")
|
||||
protected ClientSessionModel.Action action;
|
||||
|
||||
@Column(name="AUTH_USER_ID")
|
||||
protected String userId;
|
||||
|
||||
@OneToMany(cascade = CascadeType.REMOVE, orphanRemoval = true, mappedBy="clientSession")
|
||||
protected Collection<ClientSessionRoleEntity> roles = new ArrayList<ClientSessionRoleEntity>();
|
||||
|
||||
|
@ -66,6 +69,9 @@ public class ClientSessionEntity {
|
|||
@OneToMany(cascade = CascadeType.REMOVE, orphanRemoval = true, mappedBy="clientSession")
|
||||
protected Collection<ClientSessionNoteEntity> notes = new ArrayList<ClientSessionNoteEntity>();
|
||||
|
||||
@OneToMany(cascade = CascadeType.REMOVE, orphanRemoval = true, mappedBy="clientSession")
|
||||
protected Collection<ClientSessionAuthStatusEntity> authanticatorStatus = new ArrayList<>();
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
@ -153,4 +159,20 @@ public class ClientSessionEntity {
|
|||
public void setAuthMethod(String authMethod) {
|
||||
this.authMethod = authMethod;
|
||||
}
|
||||
|
||||
public Collection<ClientSessionAuthStatusEntity> getAuthanticatorStatus() {
|
||||
return authanticatorStatus;
|
||||
}
|
||||
|
||||
public void setAuthanticatorStatus(Collection<ClientSessionAuthStatusEntity> authanticatorStatus) {
|
||||
this.authanticatorStatus = authanticatorStatus;
|
||||
}
|
||||
|
||||
public String getUserId() {
|
||||
return userId;
|
||||
}
|
||||
|
||||
public void setUserId(String userId) {
|
||||
this.userId = userId;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,10 +4,12 @@ import org.keycloak.models.ClientModel;
|
|||
import org.keycloak.models.ClientSessionModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
import org.keycloak.models.sessions.mem.entities.ClientSessionEntity;
|
||||
import org.keycloak.models.sessions.mem.entities.UserSessionEntity;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
|
@ -141,4 +143,30 @@ public class ClientSessionAdapter implements ClientSessionModel {
|
|||
public void setAuthMethod(String method) {
|
||||
entity.setAuthMethod(method);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, UserSessionModel.AuthenticatorStatus> getAuthenticators() {
|
||||
return entity.getAuthenticatorStatus();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAuthenticatorStatus(String authenticator, UserSessionModel.AuthenticatorStatus status) {
|
||||
entity.getAuthenticatorStatus().put(authenticator, status);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAuthenticatorStatus(Map<String, UserSessionModel.AuthenticatorStatus> status) {
|
||||
entity.setAuthenticatorStatus(status);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserModel getAuthenticatedUser() {
|
||||
return session.users().getUserById(entity.getAuthUserId(), realm); }
|
||||
|
||||
@Override
|
||||
public void setAuthenticatedUser(UserModel user) {
|
||||
entity.setAuthUserId(user.getId());
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package org.keycloak.models.sessions.mem.entities;
|
||||
|
||||
import org.keycloak.models.ClientSessionModel;
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
@ -14,6 +15,8 @@ public class ClientSessionEntity {
|
|||
private String id;
|
||||
private String clientId;
|
||||
private String realmId;
|
||||
private Map<String, UserSessionModel.AuthenticatorStatus> authenticatorStatus = new HashMap<>();
|
||||
private String authUserId;
|
||||
|
||||
private UserSessionEntity session;
|
||||
|
||||
|
@ -24,7 +27,7 @@ public class ClientSessionEntity {
|
|||
private ClientSessionModel.Action action;
|
||||
private Set<String> roles;
|
||||
private Set<String> protocolMappers;
|
||||
private Map<String, String> notes = new HashMap<String, String>();
|
||||
private Map<String, String> notes = new HashMap<>();
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
|
@ -109,4 +112,20 @@ public class ClientSessionEntity {
|
|||
public void setAuthMethod(String authMethod) {
|
||||
this.authMethod = authMethod;
|
||||
}
|
||||
|
||||
public String getAuthUserId() {
|
||||
return authUserId;
|
||||
}
|
||||
|
||||
public void setAuthUserId(String authUserId) {
|
||||
this.authUserId = authUserId;
|
||||
}
|
||||
|
||||
public Map<String, UserSessionModel.AuthenticatorStatus> getAuthenticatorStatus() {
|
||||
return authenticatorStatus;
|
||||
}
|
||||
|
||||
public void setAuthenticatorStatus(Map<String, UserSessionModel.AuthenticatorStatus> authenticatorStatus) {
|
||||
this.authenticatorStatus = authenticatorStatus;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import org.keycloak.models.ClientModel;
|
|||
import org.keycloak.models.ClientSessionModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
import org.keycloak.models.sessions.mongo.entities.MongoClientSessionEntity;
|
||||
import org.keycloak.models.sessions.mongo.entities.MongoUserSessionEntity;
|
||||
|
@ -12,6 +13,7 @@ import org.keycloak.models.sessions.mongo.entities.MongoUserSessionEntity;
|
|||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
|
@ -154,6 +156,37 @@ public class ClientSessionAdapter extends AbstractMongoAdapter<MongoClientSessio
|
|||
updateMongoEntity();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, UserSessionModel.AuthenticatorStatus> getAuthenticators() {
|
||||
return entity.getAuthenticatorStatus();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAuthenticatorStatus(String authenticator, UserSessionModel.AuthenticatorStatus status) {
|
||||
entity.getAuthenticatorStatus().put(authenticator, status);
|
||||
updateMongoEntity();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAuthenticatorStatus(Map<String, UserSessionModel.AuthenticatorStatus> status) {
|
||||
entity.setAuthenticatorStatus(status);
|
||||
updateMongoEntity();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserModel getAuthenticatedUser() {
|
||||
return session.users().getUserById(entity.getAuthUserId(), realm);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAuthenticatedUser(UserModel user) {
|
||||
entity.setAuthUserId(user.getId());
|
||||
updateMongoEntity();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAuthMethod() {
|
||||
return entity.getAuthMethod();
|
||||
|
|
|
@ -4,6 +4,7 @@ import org.keycloak.connections.mongo.api.MongoCollection;
|
|||
import org.keycloak.connections.mongo.api.MongoIdentifiableEntity;
|
||||
import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext;
|
||||
import org.keycloak.models.ClientSessionModel;
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
import org.keycloak.models.entities.AbstractIdentifiableEntity;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
@ -29,6 +30,8 @@ public class MongoClientSessionEntity extends AbstractIdentifiableEntity impleme
|
|||
private List<String> roles;
|
||||
private List<String> protocolMappers;
|
||||
private Map<String, String> notes = new HashMap<String, String>();
|
||||
private Map<String, UserSessionModel.AuthenticatorStatus> authenticatorStatus = new HashMap<>();
|
||||
private String authUserId;
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
|
@ -118,6 +121,22 @@ public class MongoClientSessionEntity extends AbstractIdentifiableEntity impleme
|
|||
this.sessionId = sessionId;
|
||||
}
|
||||
|
||||
public Map<String, UserSessionModel.AuthenticatorStatus> getAuthenticatorStatus() {
|
||||
return authenticatorStatus;
|
||||
}
|
||||
|
||||
public void setAuthenticatorStatus(Map<String, UserSessionModel.AuthenticatorStatus> authenticatorStatus) {
|
||||
this.authenticatorStatus = authenticatorStatus;
|
||||
}
|
||||
|
||||
public String getAuthUserId() {
|
||||
return authUserId;
|
||||
}
|
||||
|
||||
public void setAuthUserId(String authUserId) {
|
||||
this.authUserId = authUserId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterRemove(MongoStoreInvocationContext context) {
|
||||
}
|
||||
|
|
358
services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
Executable file
358
services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
Executable file
|
@ -0,0 +1,358 @@
|
|||
package org.keycloak.authentication;
|
||||
|
||||
import org.jboss.resteasy.spi.HttpRequest;
|
||||
import org.keycloak.ClientConnection;
|
||||
import org.keycloak.events.EventBuilder;
|
||||
import org.keycloak.models.AuthenticatorModel;
|
||||
import org.keycloak.models.ClientSessionModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
import org.keycloak.protocol.oidc.TokenManager;
|
||||
import org.keycloak.services.managers.AuthenticationManager;
|
||||
import org.keycloak.services.managers.BruteForceProtector;
|
||||
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.UriInfo;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
|
||||
//
|
||||
// setup
|
||||
// cookie: master, alternative
|
||||
// CERT_AUTH: alternative
|
||||
// UserPassword: alternative
|
||||
// OTP: optional
|
||||
// CAPTHA: required
|
||||
//
|
||||
// scenario: username password
|
||||
// * cookie, attempted
|
||||
// * cert, attempated
|
||||
// * usernamepassord, doesn't see form, sets challenge to form
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
|
||||
|
||||
|
||||
public class AuthenticationProcessor {
|
||||
protected RealmModel realm;
|
||||
protected UserSessionModel userSession;
|
||||
protected ClientSessionModel clientSession;
|
||||
protected ClientConnection connection;
|
||||
protected UriInfo uriInfo;
|
||||
protected KeycloakSession session;
|
||||
protected List<AuthenticatorModel> authenticators;
|
||||
protected BruteForceProtector protector;
|
||||
protected EventBuilder eventBuilder;
|
||||
protected HttpRequest request;
|
||||
|
||||
|
||||
public static enum Status {
|
||||
SUCCESS,
|
||||
CHALLENGE,
|
||||
FAILURE_CHALLENGE,
|
||||
FAILED,
|
||||
ATTEMPTED
|
||||
|
||||
}
|
||||
public static enum Error {
|
||||
INVALID_USER,
|
||||
INVALID_CREDENTIALS,
|
||||
CREDENTIAL_SETUP_REQUIRED,
|
||||
USER_DISABLED,
|
||||
USER_CONFLICT,
|
||||
USER_TEMPORARILY_DISABLED,
|
||||
INTERNAL_ERROR,
|
||||
UNKNOWN_USER
|
||||
}
|
||||
|
||||
public RealmModel getRealm() {
|
||||
return realm;
|
||||
}
|
||||
|
||||
public ClientSessionModel getClientSession() {
|
||||
return clientSession;
|
||||
}
|
||||
|
||||
public ClientConnection getConnection() {
|
||||
return connection;
|
||||
}
|
||||
|
||||
public UriInfo getUriInfo() {
|
||||
return uriInfo;
|
||||
}
|
||||
|
||||
public KeycloakSession getSession() {
|
||||
return session;
|
||||
}
|
||||
|
||||
|
||||
private class Result implements AuthenticatorContext {
|
||||
AuthenticatorModel model;
|
||||
Authenticator authenticator;
|
||||
Status status;
|
||||
Response challenge;
|
||||
Error error;
|
||||
|
||||
private Result(AuthenticatorModel model, Authenticator authenticator) {
|
||||
this.model = model;
|
||||
this.authenticator = authenticator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthenticatorModel getModel() {
|
||||
return model;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setModel(AuthenticatorModel model) {
|
||||
this.model = model;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Authenticator getAuthenticator() {
|
||||
return authenticator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAuthenticator(Authenticator authenticator) {
|
||||
this.authenticator = authenticator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Status getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void success() {
|
||||
this.status = Status.SUCCESS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failure(Error error) {
|
||||
status = Status.FAILED;
|
||||
this.error = error;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void challenge(Response challenge) {
|
||||
this.status = Status.CHALLENGE;
|
||||
this.challenge = challenge;
|
||||
|
||||
}
|
||||
@Override
|
||||
public void failureChallenge(Error error, Response challenge) {
|
||||
this.error = error;
|
||||
this.status = Status.FAILURE_CHALLENGE;
|
||||
this.challenge = challenge;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void attempted() {
|
||||
this.status = Status.ATTEMPTED;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserModel getUser() {
|
||||
return getClientSession().getAuthenticatedUser();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUser(UserModel user) {
|
||||
UserModel previousUser = getUser();
|
||||
if (previousUser != null && !user.getId().equals(previousUser.getId())) throw new AuthException(Error.USER_CONFLICT);
|
||||
validateUser(user);
|
||||
getClientSession().setAuthenticatedUser(user);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RealmModel getRealm() {
|
||||
return AuthenticationProcessor.this.getRealm();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientSessionModel getClientSession() {
|
||||
return AuthenticationProcessor.this.getClientSession();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientConnection getConnection() {
|
||||
return AuthenticationProcessor.this.getConnection();
|
||||
}
|
||||
|
||||
@Override
|
||||
public UriInfo getUriInfo() {
|
||||
return AuthenticationProcessor.this.getUriInfo();
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeycloakSession getSession() {
|
||||
return AuthenticationProcessor.this.getSession();
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpRequest getHttpRequest() {
|
||||
return AuthenticationProcessor.this.request;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void attachUserSession(UserSessionModel userSession) {
|
||||
AuthenticationProcessor.this.userSession = userSession;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BruteForceProtector getProtector() {
|
||||
return AuthenticationProcessor.this.protector;
|
||||
}
|
||||
}
|
||||
|
||||
public static class AuthException extends RuntimeException {
|
||||
private Error error;
|
||||
|
||||
public AuthException(Error error) {
|
||||
this.error = error;
|
||||
}
|
||||
|
||||
public AuthException(String message, Error error) {
|
||||
super(message);
|
||||
this.error = error;
|
||||
}
|
||||
|
||||
public AuthException(String message, Throwable cause, Error error) {
|
||||
super(message, cause);
|
||||
this.error = error;
|
||||
}
|
||||
|
||||
public AuthException(Throwable cause, Error error) {
|
||||
super(cause);
|
||||
this.error = error;
|
||||
}
|
||||
|
||||
public AuthException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace, Error error) {
|
||||
super(message, cause, enableSuppression, writableStackTrace);
|
||||
this.error = error;
|
||||
}
|
||||
}
|
||||
|
||||
public void logUserFailure() {
|
||||
|
||||
}
|
||||
|
||||
protected boolean isProcessed(UserSessionModel.AuthenticatorStatus status) {
|
||||
return status == UserSessionModel.AuthenticatorStatus.SUCCESS || status == UserSessionModel.AuthenticatorStatus.SKIPPED
|
||||
|| status == UserSessionModel.AuthenticatorStatus.ATTEMPTED
|
||||
|| status == UserSessionModel.AuthenticatorStatus.SETUP_REQUIRED;
|
||||
}
|
||||
|
||||
public Response authenticate() {
|
||||
UserModel authUser = clientSession.getAuthenticatedUser();
|
||||
validateUser(authUser);
|
||||
Response challenge = null;
|
||||
Map<String, UserSessionModel.AuthenticatorStatus> previousAttempts = clientSession.getAuthenticators();
|
||||
for (AuthenticatorModel model : authenticators) {
|
||||
UserSessionModel.AuthenticatorStatus oldStatus = previousAttempts.get(model.getAlias());
|
||||
if (isProcessed(oldStatus)) continue;
|
||||
|
||||
AuthenticatorFactory factory = (AuthenticatorFactory)session.getKeycloakSessionFactory().getProviderFactory(Authenticator.class, model.getProviderId());
|
||||
Authenticator authenticator = factory.create(model);
|
||||
if (authenticator.requiresUser() && authUser == null){
|
||||
if ( authenticator.requiresUser()) {
|
||||
if (challenge != null) return challenge;
|
||||
throw new AuthException(Error.UNKNOWN_USER);
|
||||
}
|
||||
}
|
||||
if (authUser != null && model.getRequirement() == AuthenticatorModel.Requirement.ALTERNATIVE) {
|
||||
clientSession.setAuthenticatorStatus(model.getAlias(), UserSessionModel.AuthenticatorStatus.SKIPPED);
|
||||
continue;
|
||||
}
|
||||
authUser = clientSession.getAuthenticatedUser();
|
||||
|
||||
if (authenticator.requiresUser() && authUser != null && !authenticator.configuredFor(authUser)) {
|
||||
if (model.getRequirement() == AuthenticatorModel.Requirement.REQUIRED) {
|
||||
if (model.isUserSetupAllowed()) {
|
||||
clientSession.setAuthenticatorStatus(model.getAlias(), UserSessionModel.AuthenticatorStatus.SETUP_REQUIRED);
|
||||
authUser.addRequiredAction(authenticator.getRequiredAction());
|
||||
|
||||
} else {
|
||||
throw new AuthException(Error.CREDENTIAL_SETUP_REQUIRED);
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
Result context = new Result(model, authenticator);
|
||||
authenticator.authenticate(context);
|
||||
Status result = context.getStatus();
|
||||
if (result == Status.SUCCESS){
|
||||
clientSession.setAuthenticatorStatus(model.getAlias(), UserSessionModel.AuthenticatorStatus.SUCCESS);
|
||||
if (model.isMasterAuthenticator()) return authenticationComplete();
|
||||
continue;
|
||||
} else if (result == Status.FAILED) {
|
||||
throw new AuthException(context.error);
|
||||
} else if (result == Status.CHALLENGE) {
|
||||
if (model.getRequirement() == AuthenticatorModel.Requirement.REQUIRED) return context.challenge;
|
||||
if (challenge != null) challenge = context.challenge;
|
||||
continue;
|
||||
} else if (result == Status.FAILURE_CHALLENGE) {
|
||||
logUserFailure();
|
||||
return context.challenge;
|
||||
} else if (result == Status.ATTEMPTED) {
|
||||
if (model.getRequirement() == AuthenticatorModel.Requirement.REQUIRED) throw new AuthException(Error.INVALID_CREDENTIALS);
|
||||
clientSession.setAuthenticatorStatus(model.getAlias(), UserSessionModel.AuthenticatorStatus.ATTEMPTED);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (authUser == null) {
|
||||
if (challenge != null) return challenge;
|
||||
throw new AuthException(Error.UNKNOWN_USER);
|
||||
}
|
||||
|
||||
|
||||
return authenticationComplete();
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void validateUser(UserModel authenticatedUser) {
|
||||
if (authenticatedUser != null) {
|
||||
if (!clientSession.getAuthenticatedUser().isEnabled()) throw new AuthException(Error.USER_DISABLED);
|
||||
}
|
||||
if (realm.isBruteForceProtected()) {
|
||||
if (protector.isTemporarilyDisabled(session, realm, authenticatedUser.getUsername())) {
|
||||
throw new AuthException(Error.USER_TEMPORARILY_DISABLED);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected Response authenticationComplete() {
|
||||
if (userSession == null) { // if no authenticator attached a usersession
|
||||
userSession = session.sessions().createUserSession(realm, clientSession.getAuthenticatedUser(), clientSession.getAuthenticatedUser().getUsername(), connection.getRemoteAddr(), "form", false, null, null);
|
||||
userSession.setState(UserSessionModel.State.LOGGING_IN);
|
||||
}
|
||||
TokenManager.attachClientSession(userSession, clientSession);
|
||||
return processRequiredActions();
|
||||
|
||||
}
|
||||
|
||||
public Response processRequiredActions() {
|
||||
return AuthenticationManager.nextActionAfterAuthentication(session, userSession, clientSession, connection, request, uriInfo, eventBuilder);
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
17
services/src/main/java/org/keycloak/authentication/Authenticator.java
Executable file
17
services/src/main/java/org/keycloak/authentication/Authenticator.java
Executable file
|
@ -0,0 +1,17 @@
|
|||
package org.keycloak.authentication;
|
||||
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.provider.Provider;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public interface Authenticator extends Provider {
|
||||
boolean requiresUser();
|
||||
void authenticate(AuthenticatorContext context);
|
||||
boolean configuredFor(UserModel user);
|
||||
String getRequiredAction();
|
||||
|
||||
|
||||
}
|
54
services/src/main/java/org/keycloak/authentication/AuthenticatorContext.java
Executable file
54
services/src/main/java/org/keycloak/authentication/AuthenticatorContext.java
Executable file
|
@ -0,0 +1,54 @@
|
|||
package org.keycloak.authentication;
|
||||
|
||||
import org.jboss.resteasy.spi.HttpRequest;
|
||||
import org.keycloak.ClientConnection;
|
||||
import org.keycloak.models.AuthenticatorModel;
|
||||
import org.keycloak.models.ClientSessionModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
import org.keycloak.services.managers.BruteForceProtector;
|
||||
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.UriInfo;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public interface AuthenticatorContext {
|
||||
AuthenticatorModel getModel();
|
||||
|
||||
void setModel(AuthenticatorModel model);
|
||||
|
||||
Authenticator getAuthenticator();
|
||||
|
||||
void setAuthenticator(Authenticator authenticator);
|
||||
|
||||
AuthenticationProcessor.Status getStatus();
|
||||
|
||||
UserModel getUser();
|
||||
|
||||
void setUser(UserModel user);
|
||||
|
||||
RealmModel getRealm();
|
||||
|
||||
ClientSessionModel getClientSession();
|
||||
void attachUserSession(UserSessionModel userSession);
|
||||
|
||||
ClientConnection getConnection();
|
||||
|
||||
UriInfo getUriInfo();
|
||||
|
||||
KeycloakSession getSession();
|
||||
|
||||
HttpRequest getHttpRequest();
|
||||
BruteForceProtector getProtector();
|
||||
|
||||
void success();
|
||||
void failure(AuthenticationProcessor.Error error);
|
||||
void challenge(Response challenge);
|
||||
void failureChallenge(AuthenticationProcessor.Error error, Response challenge);
|
||||
void attempted();
|
||||
}
|
13
services/src/main/java/org/keycloak/authentication/AuthenticatorFactory.java
Executable file
13
services/src/main/java/org/keycloak/authentication/AuthenticatorFactory.java
Executable file
|
@ -0,0 +1,13 @@
|
|||
package org.keycloak.authentication;
|
||||
|
||||
import org.keycloak.models.AuthenticatorModel;
|
||||
import org.keycloak.provider.ProviderFactory;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public interface AuthenticatorFactory extends ProviderFactory<Authenticator> {
|
||||
Authenticator create(AuthenticatorModel model);
|
||||
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
package org.keycloak.authentication.authenticators;
|
||||
|
||||
import org.keycloak.authentication.Authenticator;
|
||||
import org.keycloak.authentication.AuthenticatorContext;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.services.managers.AuthenticationManager;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class CookieAuthenticator implements Authenticator {
|
||||
|
||||
@Override
|
||||
public boolean requiresUser() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void authenticate(AuthenticatorContext context) {
|
||||
AuthenticationManager.AuthResult authResult = AuthenticationManager.authenticateIdentityCookie(context.getSession(),
|
||||
context.getRealm(), context.getUriInfo(), context.getConnection(), context.getHttpRequest().getHttpHeaders(), true);
|
||||
if (authResult == null) {
|
||||
context.attempted();
|
||||
} else {
|
||||
context.setUser(authResult.getUser());
|
||||
context.attachUserSession(authResult.getSession());
|
||||
context.success();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean configuredFor(UserModel user) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRequiredAction() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
package org.keycloak.authentication.authenticators;
|
||||
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.authentication.Authenticator;
|
||||
import org.keycloak.authentication.AuthenticatorFactory;
|
||||
import org.keycloak.models.AuthenticatorModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class CookieAuthenticatorFactory implements AuthenticatorFactory {
|
||||
static CookieAuthenticator SINGLETON = new CookieAuthenticator();
|
||||
@Override
|
||||
public Authenticator create(AuthenticatorModel model) {
|
||||
return SINGLETON;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Authenticator create(KeycloakSession session) {
|
||||
throw new IllegalStateException("illegal call");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Config.Scope config) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postInit(KeycloakSessionFactory factory) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "auth-cookie";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
package org.keycloak.authentication.authenticators;
|
||||
|
||||
import org.keycloak.authentication.AuthenticationProcessor;
|
||||
import org.keycloak.authentication.AuthenticatorContext;
|
||||
import org.keycloak.models.AuthenticatorModel;
|
||||
import org.keycloak.models.UserCredentialModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.representations.idm.CredentialRepresentation;
|
||||
|
||||
import javax.ws.rs.core.MultivaluedMap;
|
||||
import javax.ws.rs.core.Response;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class LoginFormOTPAuthenticator extends LoginFormUsernameAuthenticator {
|
||||
|
||||
public LoginFormOTPAuthenticator(AuthenticatorModel model) {
|
||||
super(model);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void authenticate(AuthenticatorContext context) {
|
||||
if (!isActionUrl(context)) {
|
||||
context.failure(AuthenticationProcessor.Error.INTERNAL_ERROR);
|
||||
return;
|
||||
}
|
||||
validateOTP(context);
|
||||
}
|
||||
|
||||
public void validateOTP(AuthenticatorContext context) {
|
||||
MultivaluedMap<String, String> inputData = context.getHttpRequest().getFormParameters();
|
||||
List<UserCredentialModel> credentials = new LinkedList<>();
|
||||
String password = inputData.getFirst(CredentialRepresentation.TOTP);
|
||||
if (password == null) {
|
||||
Response challengeResponse = challenge(context);
|
||||
context.failureChallenge(AuthenticationProcessor.Error.INVALID_CREDENTIALS, challengeResponse);
|
||||
return;
|
||||
}
|
||||
credentials.add(UserCredentialModel.totp(password));
|
||||
boolean valid = context.getSession().users().validCredentials(context.getRealm(), context.getUser(), credentials);
|
||||
if (!valid) {
|
||||
Response challengeResponse = challenge(context);
|
||||
context.failureChallenge(AuthenticationProcessor.Error.INVALID_CREDENTIALS, challengeResponse);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean requiresUser() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean configuredFor(UserModel user) {
|
||||
return user.configuredForCredentialType(UserCredentialModel.TOTP);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRequiredAction() {
|
||||
return UserModel.RequiredAction.CONFIGURE_TOTP.name();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
package org.keycloak.authentication.authenticators;
|
||||
|
||||
import org.keycloak.authentication.AuthenticationProcessor;
|
||||
import org.keycloak.authentication.AuthenticatorContext;
|
||||
import org.keycloak.models.AuthenticatorModel;
|
||||
import org.keycloak.models.UserCredentialModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.representations.idm.CredentialRepresentation;
|
||||
|
||||
import javax.ws.rs.core.MultivaluedMap;
|
||||
import javax.ws.rs.core.Response;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class LoginFormPasswordAuthenticator extends LoginFormUsernameAuthenticator {
|
||||
|
||||
public LoginFormPasswordAuthenticator(AuthenticatorModel model) {
|
||||
super(model);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void authenticate(AuthenticatorContext context) {
|
||||
if (!isActionUrl(context)) {
|
||||
context.failure(AuthenticationProcessor.Error.INTERNAL_ERROR);
|
||||
return;
|
||||
}
|
||||
validatePassword(context);
|
||||
}
|
||||
|
||||
public void validatePassword(AuthenticatorContext context) {
|
||||
MultivaluedMap<String, String> inputData = context.getHttpRequest().getFormParameters();
|
||||
List<UserCredentialModel> credentials = new LinkedList<>();
|
||||
String password = inputData.getFirst(CredentialRepresentation.PASSWORD);
|
||||
if (password == null) {
|
||||
Response challengeResponse = challenge(context);
|
||||
context.failureChallenge(AuthenticationProcessor.Error.INVALID_CREDENTIALS, challengeResponse);
|
||||
return;
|
||||
}
|
||||
credentials.add(UserCredentialModel.password(password));
|
||||
boolean valid = context.getSession().users().validCredentials(context.getRealm(), context.getUser(), credentials);
|
||||
if (!valid) {
|
||||
Response challengeResponse = challenge(context);
|
||||
context.failureChallenge(AuthenticationProcessor.Error.INVALID_CREDENTIALS, challengeResponse);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean requiresUser() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean configuredFor(UserModel user) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRequiredAction() {
|
||||
return UserModel.RequiredAction.UPDATE_PASSWORD.name();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,121 @@
|
|||
package org.keycloak.authentication.authenticators;
|
||||
|
||||
import org.jboss.resteasy.specimpl.MultivaluedMapImpl;
|
||||
import org.keycloak.authentication.AuthenticationProcessor;
|
||||
import org.keycloak.authentication.Authenticator;
|
||||
import org.keycloak.authentication.AuthenticatorContext;
|
||||
import org.keycloak.login.LoginFormsProvider;
|
||||
import org.keycloak.models.AuthenticatorModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||
import org.keycloak.services.managers.AuthenticationManager;
|
||||
import org.keycloak.services.managers.ClientSessionCode;
|
||||
import org.keycloak.services.resources.LoginActionsService;
|
||||
|
||||
import javax.ws.rs.core.MultivaluedMap;
|
||||
import javax.ws.rs.core.Response;
|
||||
import java.net.URI;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class LoginFormUsernameAuthenticator implements Authenticator {
|
||||
protected AuthenticatorModel model;
|
||||
|
||||
public LoginFormUsernameAuthenticator(AuthenticatorModel model) {
|
||||
this.model = model;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void authenticate(AuthenticatorContext context) {
|
||||
if (!isActionUrl(context)) {
|
||||
MultivaluedMap<String, String> formData = new MultivaluedMapImpl<>();
|
||||
String loginHint = context.getClientSession().getNote(OIDCLoginProtocol.LOGIN_HINT_PARAM);
|
||||
if (loginHint == null) {
|
||||
loginHint = AuthenticationManager.getRememberMeUsername(context.getRealm(), context.getHttpRequest().getHttpHeaders());
|
||||
if (loginHint != null) {
|
||||
formData.add("rememberMe", "on");
|
||||
}
|
||||
}
|
||||
if (loginHint != null) formData.add(AuthenticationManager.FORM_USERNAME, loginHint);
|
||||
Response challengeResponse = challenge(context, formData);
|
||||
context.challenge(challengeResponse);
|
||||
return;
|
||||
}
|
||||
validateUser(context);
|
||||
}
|
||||
|
||||
protected boolean isActionUrl(AuthenticatorContext context) {
|
||||
URI expected = LoginActionsService.authenticationFormProcessor(context.getUriInfo()).build(context.getRealm().getName());
|
||||
return expected.getPath().equals(context.getUriInfo().getPath());
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean requiresUser() {
|
||||
return false;
|
||||
}
|
||||
|
||||
protected Response challenge(AuthenticatorContext context, MultivaluedMap<String, String> formData) {
|
||||
LoginFormsProvider forms = context.getSession().getProvider(LoginFormsProvider.class)
|
||||
.setClientSessionCode(new ClientSessionCode(context.getRealm(), context.getClientSession()).getCode());
|
||||
|
||||
if (formData.size() > 0) forms.setFormData(formData);
|
||||
|
||||
return forms.createLogin();
|
||||
}
|
||||
|
||||
public void validateUser(AuthenticatorContext context) {
|
||||
MultivaluedMap<String, String> inputData = context.getHttpRequest().getFormParameters();
|
||||
String username = inputData.getFirst(AuthenticationManager.FORM_USERNAME);
|
||||
if (username == null) {
|
||||
Response challengeResponse = challenge(context);
|
||||
context.failureChallenge(AuthenticationProcessor.Error.INVALID_USER, challengeResponse);
|
||||
return;
|
||||
}
|
||||
UserModel user = KeycloakModelUtils.findUserByNameOrEmail(context.getSession(), context.getRealm(), username);
|
||||
if (invalidUser(context, user)) return;
|
||||
context.setUser(user);
|
||||
}
|
||||
|
||||
public boolean invalidUser(AuthenticatorContext context, UserModel user) {
|
||||
if (user == null) {
|
||||
Response challengeResponse = challenge(context);
|
||||
context.failureChallenge(AuthenticationProcessor.Error.INVALID_USER, challengeResponse);
|
||||
return true;
|
||||
}
|
||||
if (!user.isEnabled()) {
|
||||
context.failure(AuthenticationProcessor.Error.USER_DISABLED);
|
||||
return true;
|
||||
}
|
||||
if (context.getRealm().isBruteForceProtected()) {
|
||||
if (context.getProtector().isTemporarilyDisabled(context.getSession(), context.getRealm(), user.getUsername())) {
|
||||
context.failure(AuthenticationProcessor.Error.USER_TEMPORARILY_DISABLED);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public Response challenge(AuthenticatorContext context) {
|
||||
MultivaluedMap<String, String> formData = new MultivaluedMapImpl<>();
|
||||
return challenge(context, formData);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean configuredFor(UserModel user) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRequiredAction() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,97 @@
|
|||
package org.keycloak.authentication.authenticators;
|
||||
|
||||
import org.jboss.resteasy.specimpl.MultivaluedMapImpl;
|
||||
import org.keycloak.authentication.AuthenticationProcessor;
|
||||
import org.keycloak.authentication.Authenticator;
|
||||
import org.keycloak.authentication.AuthenticatorContext;
|
||||
import org.keycloak.login.LoginFormsProvider;
|
||||
import org.keycloak.models.AuthenticatorModel;
|
||||
import org.keycloak.models.UserCredentialModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||
import org.keycloak.representations.idm.CredentialRepresentation;
|
||||
import org.keycloak.services.managers.AuthenticationManager;
|
||||
import org.keycloak.services.managers.ClientSessionCode;
|
||||
import org.keycloak.services.resources.LoginActionsService;
|
||||
|
||||
import javax.ws.rs.core.MultivaluedMap;
|
||||
import javax.ws.rs.core.Response;
|
||||
import java.net.URI;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class OTPFormAuthenticator implements Authenticator {
|
||||
protected AuthenticatorModel model;
|
||||
|
||||
public OTPFormAuthenticator(AuthenticatorModel model) {
|
||||
this.model = model;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void authenticate(AuthenticatorContext context) {
|
||||
URI expected = LoginActionsService.authenticationFormProcessor(context.getUriInfo()).build(context.getRealm().getName());
|
||||
if (!expected.getPath().equals(context.getUriInfo().getPath())) {
|
||||
Response challengeResponse = challenge(context);
|
||||
context.challenge(challengeResponse);
|
||||
return;
|
||||
}
|
||||
validateOTP(context);
|
||||
}
|
||||
|
||||
public void validateOTP(AuthenticatorContext context) {
|
||||
MultivaluedMap<String, String> inputData = context.getHttpRequest().getFormParameters();
|
||||
List<UserCredentialModel> credentials = new LinkedList<>();
|
||||
String password = inputData.getFirst(CredentialRepresentation.TOTP);
|
||||
if (password == null) {
|
||||
Response challengeResponse = challenge(context);
|
||||
context.failureChallenge(AuthenticationProcessor.Error.INVALID_CREDENTIALS, challengeResponse);
|
||||
return;
|
||||
}
|
||||
credentials.add(UserCredentialModel.totp(password));
|
||||
boolean valid = context.getSession().users().validCredentials(context.getRealm(), context.getUser(), credentials);
|
||||
if (!valid) {
|
||||
Response challengeResponse = challenge(context);
|
||||
context.failureChallenge(AuthenticationProcessor.Error.INVALID_CREDENTIALS, challengeResponse);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean requiresUser() {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected Response challenge(AuthenticatorContext context, MultivaluedMap<String, String> formData) {
|
||||
LoginFormsProvider forms = context.getSession().getProvider(LoginFormsProvider.class)
|
||||
.setClientSessionCode(new ClientSessionCode(context.getRealm(), context.getClientSession()).getCode());
|
||||
|
||||
if (formData.size() > 0) forms.setFormData(formData);
|
||||
|
||||
return forms.createLoginTotp();
|
||||
}
|
||||
|
||||
public Response challenge(AuthenticatorContext context) {
|
||||
MultivaluedMap<String, String> formData = new MultivaluedMapImpl<>();
|
||||
return challenge(context, formData);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean configuredFor(UserModel user) {
|
||||
return user.configuredForCredentialType(UserCredentialModel.TOTP);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRequiredAction() {
|
||||
return UserModel.RequiredAction.CONFIGURE_TOTP.name();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
|
||||
}
|
||||
}
|
|
@ -406,7 +406,7 @@ public class AuthenticationManager {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (userSession.getState() != UserSessionModel.State.LOGGED_IN) userSession.setState(UserSessionModel.State.LOGGED_IN);
|
||||
// refresh the cookies!
|
||||
createLoginCookie(realm, userSession.getUser(), userSession, uriInfo, clientConnection);
|
||||
if (userSession.isRememberMe()) createRememberMeCookie(realm, userSession.getUser().getUsername(), uriInfo, clientConnection);
|
||||
|
@ -434,12 +434,12 @@ public class AuthenticationManager {
|
|||
|
||||
event.detail(Details.CODE_ID, clientSession.getId());
|
||||
|
||||
Set<UserModel.RequiredAction> requiredActions = user.getRequiredActions();
|
||||
Set<String> requiredActions = user.getRequiredActions();
|
||||
if (!requiredActions.isEmpty()) {
|
||||
Iterator<RequiredAction> i = user.getRequiredActions().iterator();
|
||||
UserModel.RequiredAction action = i.next();
|
||||
Iterator<String> i = user.getRequiredActions().iterator();
|
||||
String action = i.next();
|
||||
|
||||
if (action.equals(UserModel.RequiredAction.VERIFY_EMAIL) && Validation.isEmpty(user.getEmail())) {
|
||||
if (action.equals(UserModel.RequiredAction.VERIFY_EMAIL.name()) && Validation.isEmpty(user.getEmail())) {
|
||||
if (i.hasNext())
|
||||
action = i.next();
|
||||
else
|
||||
|
@ -447,16 +447,16 @@ public class AuthenticationManager {
|
|||
}
|
||||
|
||||
if (action != null) {
|
||||
accessCode.setRequiredAction(action);
|
||||
accessCode.setRequiredAction(RequiredAction.valueOf(action));
|
||||
|
||||
LoginFormsProvider loginFormsProvider = session.getProvider(LoginFormsProvider.class).setClientSessionCode(accessCode.getCode())
|
||||
.setUser(user);
|
||||
if (action.equals(UserModel.RequiredAction.VERIFY_EMAIL)) {
|
||||
if (action.equals(UserModel.RequiredAction.VERIFY_EMAIL.name())) {
|
||||
event.clone().event(EventType.SEND_VERIFY_EMAIL).detail(Details.EMAIL, user.getEmail()).success();
|
||||
LoginActionsService.createActionCookie(realm, uriInfo, clientConnection, userSession.getId());
|
||||
}
|
||||
|
||||
return loginFormsProvider.createResponse(action);
|
||||
return loginFormsProvider.createResponse(RequiredAction.valueOf(action));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -117,6 +117,10 @@ public class LoginActionsService {
|
|||
return loginActionsBaseUrl(baseUriBuilder);
|
||||
}
|
||||
|
||||
public static UriBuilder authenticationFormProcessor(UriInfo uriInfo) {
|
||||
return loginActionsBaseUrl(uriInfo).path("auth-form");
|
||||
}
|
||||
|
||||
public static UriBuilder loginActionsBaseUrl(UriBuilder baseUriBuilder) {
|
||||
return baseUriBuilder.path(RealmsResource.class).path(RealmsResource.class, "getLoginActionsService");
|
||||
}
|
||||
|
|
|
@ -151,7 +151,7 @@ public abstract class AbstractIdentityProviderTest {
|
|||
|
||||
UserModel federatedUser = assertSuccessfulAuthentication(identityProviderModel, "test-user-noemail", null);
|
||||
|
||||
assertTrue(federatedUser.getRequiredActions().contains(RequiredAction.VERIFY_EMAIL));
|
||||
assertTrue(federatedUser.getRequiredActions().contains(RequiredAction.VERIFY_EMAIL.name()));
|
||||
|
||||
} finally {
|
||||
getRealm().setVerifyEmail(false);
|
||||
|
|
|
@ -298,7 +298,7 @@ public abstract class AbstractKerberosTest {
|
|||
Assert.assertEquals(user.getLastName(), expectedLastname);
|
||||
|
||||
if (updateProfileActionExpected) {
|
||||
Assert.assertEquals(UserModel.RequiredAction.UPDATE_PROFILE.toString(), user.getRequiredActions().iterator().next().name());
|
||||
Assert.assertEquals(UserModel.RequiredAction.UPDATE_PROFILE.toString(), user.getRequiredActions().iterator().next());
|
||||
} else {
|
||||
Assert.assertTrue(user.getRequiredActions().isEmpty());
|
||||
}
|
||||
|
|
|
@ -110,28 +110,28 @@ public class UserModelTest extends AbstractModelTest {
|
|||
user = session.users().getUserByUsername("user", realm);
|
||||
|
||||
Assert.assertEquals(1, user.getRequiredActions().size());
|
||||
Assert.assertTrue(user.getRequiredActions().contains(RequiredAction.CONFIGURE_TOTP));
|
||||
Assert.assertTrue(user.getRequiredActions().contains(RequiredAction.CONFIGURE_TOTP.name()));
|
||||
|
||||
user.addRequiredAction(RequiredAction.CONFIGURE_TOTP);
|
||||
user = session.users().getUserByUsername("user", realm);
|
||||
|
||||
Assert.assertEquals(1, user.getRequiredActions().size());
|
||||
Assert.assertTrue(user.getRequiredActions().contains(RequiredAction.CONFIGURE_TOTP));
|
||||
Assert.assertTrue(user.getRequiredActions().contains(RequiredAction.CONFIGURE_TOTP.name()));
|
||||
|
||||
user.addRequiredAction(RequiredAction.VERIFY_EMAIL);
|
||||
user.addRequiredAction(RequiredAction.VERIFY_EMAIL.name());
|
||||
user = session.users().getUserByUsername("user", realm);
|
||||
|
||||
Assert.assertEquals(2, user.getRequiredActions().size());
|
||||
Assert.assertTrue(user.getRequiredActions().contains(RequiredAction.CONFIGURE_TOTP));
|
||||
Assert.assertTrue(user.getRequiredActions().contains(RequiredAction.VERIFY_EMAIL));
|
||||
Assert.assertTrue(user.getRequiredActions().contains(RequiredAction.CONFIGURE_TOTP.name()));
|
||||
Assert.assertTrue(user.getRequiredActions().contains(RequiredAction.VERIFY_EMAIL.name()));
|
||||
|
||||
user.removeRequiredAction(RequiredAction.CONFIGURE_TOTP);
|
||||
user.removeRequiredAction(RequiredAction.CONFIGURE_TOTP.name());
|
||||
user = session.users().getUserByUsername("user", realm);
|
||||
|
||||
Assert.assertEquals(1, user.getRequiredActions().size());
|
||||
Assert.assertTrue(user.getRequiredActions().contains(RequiredAction.VERIFY_EMAIL));
|
||||
Assert.assertTrue(user.getRequiredActions().contains(RequiredAction.VERIFY_EMAIL.name()));
|
||||
|
||||
user.removeRequiredAction(RequiredAction.VERIFY_EMAIL);
|
||||
user.removeRequiredAction(RequiredAction.VERIFY_EMAIL.name());
|
||||
user = session.users().getUserByUsername("user", realm);
|
||||
|
||||
Assert.assertTrue(user.getRequiredActions().isEmpty());
|
||||
|
@ -142,9 +142,9 @@ public class UserModelTest extends AbstractModelTest {
|
|||
Assert.assertEquals(expected.getFirstName(), actual.getFirstName());
|
||||
Assert.assertEquals(expected.getLastName(), actual.getLastName());
|
||||
|
||||
RequiredAction[] expectedRequiredActions = expected.getRequiredActions().toArray(new RequiredAction[expected.getRequiredActions().size()]);
|
||||
String[] expectedRequiredActions = expected.getRequiredActions().toArray(new String[expected.getRequiredActions().size()]);
|
||||
Arrays.sort(expectedRequiredActions);
|
||||
RequiredAction[] actualRequiredActions = actual.getRequiredActions().toArray(new RequiredAction[actual.getRequiredActions().size()]);
|
||||
String[] actualRequiredActions = actual.getRequiredActions().toArray(new String[actual.getRequiredActions().size()]);
|
||||
Arrays.sort(actualRequiredActions);
|
||||
|
||||
Assert.assertArrayEquals(expectedRequiredActions, actualRequiredActions);
|
||||
|
|
Loading…
Reference in a new issue