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.1.0.Final.xml"/>
|
||||||
<include file="META-INF/jpa-changelog-1.2.0.Beta1.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.2.0.CR1.xml"/>
|
||||||
|
<include file="META-INF/jpa-changelog-1.3.0.Beta1.xml"/>
|
||||||
</databaseChangeLog>
|
</databaseChangeLog>
|
||||||
|
|
|
@ -12,7 +12,7 @@ public interface JpaUpdaterProvider extends Provider {
|
||||||
|
|
||||||
public String FIRST_VERSION = "1.0.0.Final";
|
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();
|
public String getCurrentVersionSql();
|
||||||
|
|
||||||
|
|
|
@ -28,6 +28,7 @@
|
||||||
<!-- JpaUserSessionProvider -->
|
<!-- JpaUserSessionProvider -->
|
||||||
<class>org.keycloak.models.sessions.jpa.entities.ClientSessionEntity</class>
|
<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.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.ClientSessionProtocolMapperEntity</class>
|
||||||
<class>org.keycloak.models.sessions.jpa.entities.ClientSessionNoteEntity</class>
|
<class>org.keycloak.models.sessions.jpa.entities.ClientSessionNoteEntity</class>
|
||||||
<class>org.keycloak.models.sessions.jpa.entities.UserSessionNoteEntity</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;
|
package org.keycloak.models;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -31,6 +32,14 @@ public interface ClientSessionModel {
|
||||||
public Set<String> getProtocolMappers();
|
public Set<String> getProtocolMappers();
|
||||||
public void setProtocolMappers(Set<String> protocolMappers);
|
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.
|
* Authentication request type, i.e. OAUTH, SAML 2.0, SAML 1.1, etc.
|
||||||
*
|
*
|
||||||
|
|
|
@ -35,8 +35,12 @@ public interface UserModel {
|
||||||
|
|
||||||
Map<String, String> getAttributes();
|
Map<String, String> getAttributes();
|
||||||
|
|
||||||
Set<RequiredAction> getRequiredActions();
|
Set<String> getRequiredActions();
|
||||||
|
|
||||||
|
void addRequiredAction(String action);
|
||||||
|
|
||||||
|
void removeRequiredAction(String action);
|
||||||
|
|
||||||
void addRequiredAction(RequiredAction action);
|
void addRequiredAction(RequiredAction action);
|
||||||
|
|
||||||
void removeRequiredAction(RequiredAction action);
|
void removeRequiredAction(RequiredAction action);
|
||||||
|
@ -65,6 +69,14 @@ public interface UserModel {
|
||||||
|
|
||||||
void updateCredentialDirectly(UserCredentialValueModel cred);
|
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> getRealmRoleMappings();
|
||||||
Set<RoleModel> getClientRoleMappings(ClientModel app);
|
Set<RoleModel> getClientRoleMappings(ClientModel app);
|
||||||
boolean hasRole(RoleModel role);
|
boolean hasRole(RoleModel role);
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package org.keycloak.models;
|
package org.keycloak.models;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
@ -36,6 +37,13 @@ public interface UserSessionModel {
|
||||||
|
|
||||||
List<ClientSessionModel> getClientSessions();
|
List<ClientSessionModel> getClientSessions();
|
||||||
|
|
||||||
|
public static enum AuthenticatorStatus {
|
||||||
|
SUCCESS,
|
||||||
|
SETUP_REQUIRED,
|
||||||
|
ATTEMPTED,
|
||||||
|
SKIPPED
|
||||||
|
}
|
||||||
|
|
||||||
public String getNote(String name);
|
public String getNote(String name);
|
||||||
public void setNote(String name, String value);
|
public void setNote(String name, String value);
|
||||||
public void removeNote(String name);
|
public void removeNote(String name);
|
||||||
|
|
|
@ -24,7 +24,7 @@ public class UserEntity extends AbstractIdentifiableEntity {
|
||||||
private List<String> roleIds;
|
private List<String> roleIds;
|
||||||
|
|
||||||
private Map<String, String> attributes;
|
private Map<String, String> attributes;
|
||||||
private List<UserModel.RequiredAction> requiredActions;
|
private List<String> requiredActions;
|
||||||
private List<CredentialEntity> credentials = new ArrayList<CredentialEntity>();
|
private List<CredentialEntity> credentials = new ArrayList<CredentialEntity>();
|
||||||
private List<FederatedIdentityEntity> federatedIdentities;
|
private List<FederatedIdentityEntity> federatedIdentities;
|
||||||
private String federationLink;
|
private String federationLink;
|
||||||
|
@ -109,11 +109,11 @@ public class UserEntity extends AbstractIdentifiableEntity {
|
||||||
this.attributes = attributes;
|
this.attributes = attributes;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<UserModel.RequiredAction> getRequiredActions() {
|
public List<String> getRequiredActions() {
|
||||||
return requiredActions;
|
return requiredActions;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setRequiredActions(List<UserModel.RequiredAction> requiredActions) {
|
public void setRequiredActions(List<String> requiredActions) {
|
||||||
this.requiredActions = requiredActions;
|
this.requiredActions = requiredActions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -55,8 +55,8 @@ public class ModelToRepresentation {
|
||||||
rep.setFederationLink(user.getFederationLink());
|
rep.setFederationLink(user.getFederationLink());
|
||||||
|
|
||||||
List<String> reqActions = new ArrayList<String>();
|
List<String> reqActions = new ArrayList<String>();
|
||||||
for (UserModel.RequiredAction ra : user.getRequiredActions()){
|
for (String ra : user.getRequiredActions()){
|
||||||
reqActions.add(ra.name());
|
reqActions.add(ra);
|
||||||
}
|
}
|
||||||
|
|
||||||
rep.setRequiredActions(reqActions);
|
rep.setRequiredActions(reqActions);
|
||||||
|
|
|
@ -73,10 +73,25 @@ public class UserModelDelegate implements UserModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Set<RequiredAction> getRequiredActions() {
|
public Set<String> getRequiredActions() {
|
||||||
return delegate.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
|
@Override
|
||||||
public void addRequiredAction(RequiredAction action) {
|
public void addRequiredAction(RequiredAction action) {
|
||||||
delegate.addRequiredAction(action);
|
delegate.addRequiredAction(action);
|
||||||
|
@ -211,4 +226,5 @@ public class UserModelDelegate implements UserModel {
|
||||||
public boolean revokeConsentForClient(String clientId) {
|
public boolean revokeConsentForClient(String clientId) {
|
||||||
return delegate.revokeConsentForClient(clientId);
|
return delegate.revokeConsentForClient(clientId);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -183,28 +183,53 @@ public class UserAdapter implements UserModel, Comparable {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Set<RequiredAction> getRequiredActions() {
|
public Set<String> getRequiredActions() {
|
||||||
List<RequiredAction> requiredActions = user.getRequiredActions();
|
List<String> requiredActions = user.getRequiredActions();
|
||||||
if (requiredActions == null) requiredActions = new ArrayList<RequiredAction>();
|
if (requiredActions == null) requiredActions = new ArrayList<String>();
|
||||||
return new HashSet(requiredActions);
|
return new HashSet(requiredActions);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addRequiredAction(RequiredAction action) {
|
public void addRequiredAction(RequiredAction action) {
|
||||||
List<RequiredAction> requiredActions = user.getRequiredActions();
|
String actionName = action.name();
|
||||||
if (requiredActions == null) requiredActions = new ArrayList<RequiredAction>();
|
addRequiredAction(actionName);
|
||||||
if (!requiredActions.contains(action)) requiredActions.add(action);
|
}
|
||||||
|
|
||||||
|
@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);
|
user.setRequiredActions(requiredActions);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void removeRequiredAction(RequiredAction action) {
|
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;
|
if (requiredActions == null) return;
|
||||||
requiredActions.remove(action);
|
requiredActions.remove(actionName);
|
||||||
user.setRequiredActions(requiredActions);
|
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
|
@Override
|
||||||
public boolean isTotp() {
|
public boolean isTotp() {
|
||||||
return user.isTotp();
|
return user.isTotp();
|
||||||
|
|
|
@ -102,7 +102,7 @@ public class UserAdapter implements UserModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Set<RequiredAction> getRequiredActions() {
|
public Set<String> getRequiredActions() {
|
||||||
if (updated != null) return updated.getRequiredActions();
|
if (updated != null) return updated.getRequiredActions();
|
||||||
return cached.getRequiredActions();
|
return cached.getRequiredActions();
|
||||||
}
|
}
|
||||||
|
@ -119,6 +119,27 @@ public class UserAdapter implements UserModel {
|
||||||
updated.removeRequiredAction(action);
|
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
|
@Override
|
||||||
public String getFirstName() {
|
public String getFirstName() {
|
||||||
if (updated != null) return updated.getFirstName();
|
if (updated != null) return updated.getFirstName();
|
||||||
|
|
|
@ -28,8 +28,8 @@ public class CachedUser {
|
||||||
private boolean enabled;
|
private boolean enabled;
|
||||||
private boolean totp;
|
private boolean totp;
|
||||||
private String federationLink;
|
private String federationLink;
|
||||||
private Map<String, String> attributes = new HashMap<String, String>();
|
private Map<String, String> attributes = new HashMap<>();
|
||||||
private Set<UserModel.RequiredAction> requiredActions = new HashSet<UserModel.RequiredAction>();
|
private Set<String> requiredActions = new HashSet<>();
|
||||||
private Set<String> roleMappings = new HashSet<String>();
|
private Set<String> roleMappings = new HashSet<String>();
|
||||||
|
|
||||||
|
|
||||||
|
@ -96,7 +96,7 @@ public class CachedUser {
|
||||||
return attributes;
|
return attributes;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Set<UserModel.RequiredAction> getRequiredActions() {
|
public Set<String> getRequiredActions() {
|
||||||
return requiredActions;
|
return requiredActions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -139,8 +139,8 @@ public class UserAdapter implements UserModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Set<RequiredAction> getRequiredActions() {
|
public Set<String> getRequiredActions() {
|
||||||
Set<RequiredAction> result = new HashSet<RequiredAction>();
|
Set<String> result = new HashSet<>();
|
||||||
for (UserRequiredActionEntity attr : user.getRequiredActions()) {
|
for (UserRequiredActionEntity attr : user.getRequiredActions()) {
|
||||||
result.add(attr.getAction());
|
result.add(attr.getAction());
|
||||||
}
|
}
|
||||||
|
@ -149,13 +149,19 @@ public class UserAdapter implements UserModel {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addRequiredAction(RequiredAction action) {
|
public void addRequiredAction(RequiredAction action) {
|
||||||
|
String actionName = action.name();
|
||||||
|
addRequiredAction(actionName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addRequiredAction(String actionName) {
|
||||||
for (UserRequiredActionEntity attr : user.getRequiredActions()) {
|
for (UserRequiredActionEntity attr : user.getRequiredActions()) {
|
||||||
if (attr.getAction().equals(action)) {
|
if (attr.getAction().equals(actionName)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
UserRequiredActionEntity attr = new UserRequiredActionEntity();
|
UserRequiredActionEntity attr = new UserRequiredActionEntity();
|
||||||
attr.setAction(action);
|
attr.setAction(actionName);
|
||||||
attr.setUser(user);
|
attr.setUser(user);
|
||||||
em.persist(attr);
|
em.persist(attr);
|
||||||
user.getRequiredActions().add(attr);
|
user.getRequiredActions().add(attr);
|
||||||
|
@ -163,16 +169,31 @@ public class UserAdapter implements UserModel {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void removeRequiredAction(RequiredAction action) {
|
public void removeRequiredAction(RequiredAction action) {
|
||||||
|
String actionName = action.name();
|
||||||
|
removeRequiredAction(actionName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeRequiredAction(String actionName) {
|
||||||
Iterator<UserRequiredActionEntity> it = user.getRequiredActions().iterator();
|
Iterator<UserRequiredActionEntity> it = user.getRequiredActions().iterator();
|
||||||
while (it.hasNext()) {
|
while (it.hasNext()) {
|
||||||
UserRequiredActionEntity attr = it.next();
|
UserRequiredActionEntity attr = it.next();
|
||||||
if (attr.getAction().equals(action)) {
|
if (attr.getAction().equals(actionName)) {
|
||||||
it.remove();
|
it.remove();
|
||||||
em.remove(attr);
|
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
|
@Override
|
||||||
public String getFirstName() {
|
public String getFirstName() {
|
||||||
return user.getFirstName();
|
return user.getFirstName();
|
||||||
|
|
|
@ -33,14 +33,14 @@ public class UserRequiredActionEntity {
|
||||||
protected UserEntity user;
|
protected UserEntity user;
|
||||||
|
|
||||||
@Id
|
@Id
|
||||||
@Column(name="ACTION")
|
@Column(name="REQUIRED_ACTION")
|
||||||
protected UserModel.RequiredAction action;
|
protected String action;
|
||||||
|
|
||||||
public UserModel.RequiredAction getAction() {
|
public String getAction() {
|
||||||
return action;
|
return action;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setAction(UserModel.RequiredAction action) {
|
public void setAction(String action) {
|
||||||
this.action = action;
|
this.action = action;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,12 +56,12 @@ public class UserRequiredActionEntity {
|
||||||
|
|
||||||
protected UserEntity user;
|
protected UserEntity user;
|
||||||
|
|
||||||
protected UserModel.RequiredAction action;
|
protected String action;
|
||||||
|
|
||||||
public Key() {
|
public Key() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public Key(UserEntity user, UserModel.RequiredAction action) {
|
public Key(UserEntity user, String action) {
|
||||||
this.user = user;
|
this.user = user;
|
||||||
this.action = action;
|
this.action = action;
|
||||||
}
|
}
|
||||||
|
@ -70,7 +70,7 @@ public class UserRequiredActionEntity {
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
public UserModel.RequiredAction getAction() {
|
public String getAction() {
|
||||||
return action;
|
return action;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -159,8 +159,8 @@ public class UserAdapter extends AbstractMongoAdapter<MongoUserEntity> implement
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Set<RequiredAction> getRequiredActions() {
|
public Set<String> getRequiredActions() {
|
||||||
Set<RequiredAction> result = new HashSet<RequiredAction>();
|
Set<String> result = new HashSet<String>();
|
||||||
if (user.getRequiredActions() != null) {
|
if (user.getRequiredActions() != null) {
|
||||||
result.addAll(user.getRequiredActions());
|
result.addAll(user.getRequiredActions());
|
||||||
}
|
}
|
||||||
|
@ -169,12 +169,24 @@ public class UserAdapter extends AbstractMongoAdapter<MongoUserEntity> implement
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addRequiredAction(RequiredAction action) {
|
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
|
@Override
|
||||||
public void removeRequiredAction(RequiredAction action) {
|
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
|
@Override
|
||||||
|
@ -320,6 +332,16 @@ public class UserAdapter extends AbstractMongoAdapter<MongoUserEntity> implement
|
||||||
return result;
|
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
|
@Override
|
||||||
public void updateCredentialDirectly(UserCredentialValueModel credModel) {
|
public void updateCredentialDirectly(UserCredentialValueModel credModel) {
|
||||||
CredentialEntity credentialEntity = getCredentialEntity(user, credModel.getType());
|
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.ClientSessionModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.UserSessionModel;
|
import org.keycloak.models.UserSessionModel;
|
||||||
import org.keycloak.models.sessions.infinispan.entities.ClientSessionEntity;
|
import org.keycloak.models.sessions.infinispan.entities.ClientSessionEntity;
|
||||||
import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
|
import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -165,5 +167,30 @@ public class ClientSessionAdapter implements ClientSessionModel {
|
||||||
void update() {
|
void update() {
|
||||||
provider.getTx().replace(cache, entity.getId(), entity);
|
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;
|
package org.keycloak.models.sessions.infinispan.entities;
|
||||||
|
|
||||||
import org.keycloak.models.ClientSessionModel;
|
import org.keycloak.models.ClientSessionModel;
|
||||||
|
import org.keycloak.models.UserSessionModel;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
@ -27,6 +29,8 @@ public class ClientSessionEntity extends SessionEntity {
|
||||||
private Set<String> roles;
|
private Set<String> roles;
|
||||||
private Set<String> protocolMappers;
|
private Set<String> protocolMappers;
|
||||||
private Map<String, String> notes;
|
private Map<String, String> notes;
|
||||||
|
private Map<String, UserSessionModel.AuthenticatorStatus> authenticatorStatus = new HashMap<>();
|
||||||
|
private String authUserId;
|
||||||
|
|
||||||
public String getClient() {
|
public String getClient() {
|
||||||
return client;
|
return client;
|
||||||
|
@ -107,4 +111,20 @@ public class ClientSessionEntity extends SessionEntity {
|
||||||
public void setNotes(Map<String, String> notes) {
|
public void setNotes(Map<String, String> notes) {
|
||||||
this.notes = 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.ClientSessionModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.UserSessionModel;
|
import org.keycloak.models.UserSessionModel;
|
||||||
import org.keycloak.models.sessions.jpa.entities.ClientSessionEntity;
|
import org.keycloak.models.sessions.jpa.entities.ClientSessionEntity;
|
||||||
import org.keycloak.models.sessions.jpa.entities.ClientSessionNoteEntity;
|
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 javax.persistence.EntityManager;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Set;
|
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")
|
@Column(name="ACTION")
|
||||||
protected ClientSessionModel.Action action;
|
protected ClientSessionModel.Action action;
|
||||||
|
|
||||||
|
@Column(name="AUTH_USER_ID")
|
||||||
|
protected String userId;
|
||||||
|
|
||||||
@OneToMany(cascade = CascadeType.REMOVE, orphanRemoval = true, mappedBy="clientSession")
|
@OneToMany(cascade = CascadeType.REMOVE, orphanRemoval = true, mappedBy="clientSession")
|
||||||
protected Collection<ClientSessionRoleEntity> roles = new ArrayList<ClientSessionRoleEntity>();
|
protected Collection<ClientSessionRoleEntity> roles = new ArrayList<ClientSessionRoleEntity>();
|
||||||
|
|
||||||
|
@ -66,6 +69,9 @@ public class ClientSessionEntity {
|
||||||
@OneToMany(cascade = CascadeType.REMOVE, orphanRemoval = true, mappedBy="clientSession")
|
@OneToMany(cascade = CascadeType.REMOVE, orphanRemoval = true, mappedBy="clientSession")
|
||||||
protected Collection<ClientSessionNoteEntity> notes = new ArrayList<ClientSessionNoteEntity>();
|
protected Collection<ClientSessionNoteEntity> notes = new ArrayList<ClientSessionNoteEntity>();
|
||||||
|
|
||||||
|
@OneToMany(cascade = CascadeType.REMOVE, orphanRemoval = true, mappedBy="clientSession")
|
||||||
|
protected Collection<ClientSessionAuthStatusEntity> authanticatorStatus = new ArrayList<>();
|
||||||
|
|
||||||
public String getId() {
|
public String getId() {
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
@ -153,4 +159,20 @@ public class ClientSessionEntity {
|
||||||
public void setAuthMethod(String authMethod) {
|
public void setAuthMethod(String authMethod) {
|
||||||
this.authMethod = 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.ClientSessionModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.UserSessionModel;
|
import org.keycloak.models.UserSessionModel;
|
||||||
import org.keycloak.models.sessions.mem.entities.ClientSessionEntity;
|
import org.keycloak.models.sessions.mem.entities.ClientSessionEntity;
|
||||||
import org.keycloak.models.sessions.mem.entities.UserSessionEntity;
|
import org.keycloak.models.sessions.mem.entities.UserSessionEntity;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -141,4 +143,30 @@ public class ClientSessionAdapter implements ClientSessionModel {
|
||||||
public void setAuthMethod(String method) {
|
public void setAuthMethod(String method) {
|
||||||
entity.setAuthMethod(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;
|
package org.keycloak.models.sessions.mem.entities;
|
||||||
|
|
||||||
import org.keycloak.models.ClientSessionModel;
|
import org.keycloak.models.ClientSessionModel;
|
||||||
|
import org.keycloak.models.UserSessionModel;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
@ -14,6 +15,8 @@ public class ClientSessionEntity {
|
||||||
private String id;
|
private String id;
|
||||||
private String clientId;
|
private String clientId;
|
||||||
private String realmId;
|
private String realmId;
|
||||||
|
private Map<String, UserSessionModel.AuthenticatorStatus> authenticatorStatus = new HashMap<>();
|
||||||
|
private String authUserId;
|
||||||
|
|
||||||
private UserSessionEntity session;
|
private UserSessionEntity session;
|
||||||
|
|
||||||
|
@ -24,7 +27,7 @@ public class ClientSessionEntity {
|
||||||
private ClientSessionModel.Action action;
|
private ClientSessionModel.Action action;
|
||||||
private Set<String> roles;
|
private Set<String> roles;
|
||||||
private Set<String> protocolMappers;
|
private Set<String> protocolMappers;
|
||||||
private Map<String, String> notes = new HashMap<String, String>();
|
private Map<String, String> notes = new HashMap<>();
|
||||||
|
|
||||||
public String getId() {
|
public String getId() {
|
||||||
return id;
|
return id;
|
||||||
|
@ -109,4 +112,20 @@ public class ClientSessionEntity {
|
||||||
public void setAuthMethod(String authMethod) {
|
public void setAuthMethod(String authMethod) {
|
||||||
this.authMethod = 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.ClientSessionModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.UserSessionModel;
|
import org.keycloak.models.UserSessionModel;
|
||||||
import org.keycloak.models.sessions.mongo.entities.MongoClientSessionEntity;
|
import org.keycloak.models.sessions.mongo.entities.MongoClientSessionEntity;
|
||||||
import org.keycloak.models.sessions.mongo.entities.MongoUserSessionEntity;
|
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.HashSet;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -154,6 +156,37 @@ public class ClientSessionAdapter extends AbstractMongoAdapter<MongoClientSessio
|
||||||
updateMongoEntity();
|
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
|
@Override
|
||||||
public String getAuthMethod() {
|
public String getAuthMethod() {
|
||||||
return entity.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.MongoIdentifiableEntity;
|
||||||
import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext;
|
import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext;
|
||||||
import org.keycloak.models.ClientSessionModel;
|
import org.keycloak.models.ClientSessionModel;
|
||||||
|
import org.keycloak.models.UserSessionModel;
|
||||||
import org.keycloak.models.entities.AbstractIdentifiableEntity;
|
import org.keycloak.models.entities.AbstractIdentifiableEntity;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
@ -29,6 +30,8 @@ public class MongoClientSessionEntity extends AbstractIdentifiableEntity impleme
|
||||||
private List<String> roles;
|
private List<String> roles;
|
||||||
private List<String> protocolMappers;
|
private List<String> protocolMappers;
|
||||||
private Map<String, String> notes = new HashMap<String, String>();
|
private Map<String, String> notes = new HashMap<String, String>();
|
||||||
|
private Map<String, UserSessionModel.AuthenticatorStatus> authenticatorStatus = new HashMap<>();
|
||||||
|
private String authUserId;
|
||||||
|
|
||||||
public String getId() {
|
public String getId() {
|
||||||
return id;
|
return id;
|
||||||
|
@ -118,6 +121,22 @@ public class MongoClientSessionEntity extends AbstractIdentifiableEntity impleme
|
||||||
this.sessionId = sessionId;
|
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
|
@Override
|
||||||
public void afterRemove(MongoStoreInvocationContext context) {
|
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!
|
// refresh the cookies!
|
||||||
createLoginCookie(realm, userSession.getUser(), userSession, uriInfo, clientConnection);
|
createLoginCookie(realm, userSession.getUser(), userSession, uriInfo, clientConnection);
|
||||||
if (userSession.isRememberMe()) createRememberMeCookie(realm, userSession.getUser().getUsername(), 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());
|
event.detail(Details.CODE_ID, clientSession.getId());
|
||||||
|
|
||||||
Set<UserModel.RequiredAction> requiredActions = user.getRequiredActions();
|
Set<String> requiredActions = user.getRequiredActions();
|
||||||
if (!requiredActions.isEmpty()) {
|
if (!requiredActions.isEmpty()) {
|
||||||
Iterator<RequiredAction> i = user.getRequiredActions().iterator();
|
Iterator<String> i = user.getRequiredActions().iterator();
|
||||||
UserModel.RequiredAction action = i.next();
|
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())
|
if (i.hasNext())
|
||||||
action = i.next();
|
action = i.next();
|
||||||
else
|
else
|
||||||
|
@ -447,16 +447,16 @@ public class AuthenticationManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (action != null) {
|
if (action != null) {
|
||||||
accessCode.setRequiredAction(action);
|
accessCode.setRequiredAction(RequiredAction.valueOf(action));
|
||||||
|
|
||||||
LoginFormsProvider loginFormsProvider = session.getProvider(LoginFormsProvider.class).setClientSessionCode(accessCode.getCode())
|
LoginFormsProvider loginFormsProvider = session.getProvider(LoginFormsProvider.class).setClientSessionCode(accessCode.getCode())
|
||||||
.setUser(user);
|
.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();
|
event.clone().event(EventType.SEND_VERIFY_EMAIL).detail(Details.EMAIL, user.getEmail()).success();
|
||||||
LoginActionsService.createActionCookie(realm, uriInfo, clientConnection, userSession.getId());
|
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);
|
return loginActionsBaseUrl(baseUriBuilder);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static UriBuilder authenticationFormProcessor(UriInfo uriInfo) {
|
||||||
|
return loginActionsBaseUrl(uriInfo).path("auth-form");
|
||||||
|
}
|
||||||
|
|
||||||
public static UriBuilder loginActionsBaseUrl(UriBuilder baseUriBuilder) {
|
public static UriBuilder loginActionsBaseUrl(UriBuilder baseUriBuilder) {
|
||||||
return baseUriBuilder.path(RealmsResource.class).path(RealmsResource.class, "getLoginActionsService");
|
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);
|
UserModel federatedUser = assertSuccessfulAuthentication(identityProviderModel, "test-user-noemail", null);
|
||||||
|
|
||||||
assertTrue(federatedUser.getRequiredActions().contains(RequiredAction.VERIFY_EMAIL));
|
assertTrue(federatedUser.getRequiredActions().contains(RequiredAction.VERIFY_EMAIL.name()));
|
||||||
|
|
||||||
} finally {
|
} finally {
|
||||||
getRealm().setVerifyEmail(false);
|
getRealm().setVerifyEmail(false);
|
||||||
|
|
|
@ -298,7 +298,7 @@ public abstract class AbstractKerberosTest {
|
||||||
Assert.assertEquals(user.getLastName(), expectedLastname);
|
Assert.assertEquals(user.getLastName(), expectedLastname);
|
||||||
|
|
||||||
if (updateProfileActionExpected) {
|
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 {
|
} else {
|
||||||
Assert.assertTrue(user.getRequiredActions().isEmpty());
|
Assert.assertTrue(user.getRequiredActions().isEmpty());
|
||||||
}
|
}
|
||||||
|
|
|
@ -110,28 +110,28 @@ public class UserModelTest extends AbstractModelTest {
|
||||||
user = session.users().getUserByUsername("user", realm);
|
user = session.users().getUserByUsername("user", realm);
|
||||||
|
|
||||||
Assert.assertEquals(1, user.getRequiredActions().size());
|
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.addRequiredAction(RequiredAction.CONFIGURE_TOTP);
|
||||||
user = session.users().getUserByUsername("user", realm);
|
user = session.users().getUserByUsername("user", realm);
|
||||||
|
|
||||||
Assert.assertEquals(1, user.getRequiredActions().size());
|
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);
|
user = session.users().getUserByUsername("user", realm);
|
||||||
|
|
||||||
Assert.assertEquals(2, user.getRequiredActions().size());
|
Assert.assertEquals(2, user.getRequiredActions().size());
|
||||||
Assert.assertTrue(user.getRequiredActions().contains(RequiredAction.CONFIGURE_TOTP));
|
Assert.assertTrue(user.getRequiredActions().contains(RequiredAction.CONFIGURE_TOTP.name()));
|
||||||
Assert.assertTrue(user.getRequiredActions().contains(RequiredAction.VERIFY_EMAIL));
|
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);
|
user = session.users().getUserByUsername("user", realm);
|
||||||
|
|
||||||
Assert.assertEquals(1, user.getRequiredActions().size());
|
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);
|
user = session.users().getUserByUsername("user", realm);
|
||||||
|
|
||||||
Assert.assertTrue(user.getRequiredActions().isEmpty());
|
Assert.assertTrue(user.getRequiredActions().isEmpty());
|
||||||
|
@ -142,9 +142,9 @@ public class UserModelTest extends AbstractModelTest {
|
||||||
Assert.assertEquals(expected.getFirstName(), actual.getFirstName());
|
Assert.assertEquals(expected.getFirstName(), actual.getFirstName());
|
||||||
Assert.assertEquals(expected.getLastName(), actual.getLastName());
|
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);
|
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);
|
Arrays.sort(actualRequiredActions);
|
||||||
|
|
||||||
Assert.assertArrayEquals(expectedRequiredActions, actualRequiredActions);
|
Assert.assertArrayEquals(expectedRequiredActions, actualRequiredActions);
|
||||||
|
|
Loading…
Reference in a new issue