keycloak-aplcache
Changes
model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/UserAdapter.java 9(+0 -9)
model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/ClientSessionAdapter.java 21(+21 -0)
model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/ClientSessionEntity.java 9(+9 -0)
model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/ClientSessionAdapter.java 28(+28 -0)
model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/entities/ClientSessionEntity.java 11(+11 -0)
model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/entities/ClientUserSessionNoteEntity.java 109(+109 -0)
model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/ClientSessionAdapter.java 10(+10 -0)
model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/entities/ClientSessionEntity.java 5(+5 -0)
model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/ClientSessionAdapter.java 14(+14 -0)
model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/entities/MongoClientSessionEntity.java 9(+9 -0)
services/src/main/java/org/keycloak/authentication/authenticators/AbstractFormAuthenticator.java 80(+80 -0)
services/src/main/java/org/keycloak/authentication/authenticators/AuthenticationFlow.java 60(+0 -60)
services/src/main/java/org/keycloak/authentication/authenticators/LoginFormOTPAuthenticator.java 6(+4 -2)
services/src/main/java/org/keycloak/authentication/authenticators/LoginFormPasswordAuthenticator.java 9(+7 -2)
services/src/main/java/org/keycloak/authentication/authenticators/LoginFormUsernameAuthenticator.java 90(+23 -67)
services/src/main/java/org/keycloak/authentication/authenticators/OTPFormAuthenticator.java 31(+14 -17)
services/src/main/java/org/keycloak/authentication/authenticators/SpnegoAuthenticator.java 123(+123 -0)
services/src/main/java/org/keycloak/authentication/authenticators/SpnegoAuthenticatorFactory.java 70(+70 -0)
services/src/main/resources/META-INF/services/org.keycloak.authentication.AuthenticatorFactory 3(+2 -1)
testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionTotpSetupTest.java 9(+6 -3)
testsuite/integration/src/test/java/org/keycloak/testsuite/federation/AbstractKerberosTest.java 4(+2 -2)
Details
diff --git a/connections/jpa/src/main/resources/META-INF/persistence.xml b/connections/jpa/src/main/resources/META-INF/persistence.xml
index be9c281..bb38914 100755
--- a/connections/jpa/src/main/resources/META-INF/persistence.xml
+++ b/connections/jpa/src/main/resources/META-INF/persistence.xml
@@ -35,6 +35,7 @@
<class>org.keycloak.models.sessions.jpa.entities.ClientSessionAuthStatusEntity</class>
<class>org.keycloak.models.sessions.jpa.entities.ClientSessionProtocolMapperEntity</class>
<class>org.keycloak.models.sessions.jpa.entities.ClientSessionNoteEntity</class>
+ <class>org.keycloak.models.sessions.jpa.entities.ClientUserSessionNoteEntity</class>
<class>org.keycloak.models.sessions.jpa.entities.UserSessionNoteEntity</class>
<class>org.keycloak.models.sessions.jpa.entities.UserSessionEntity</class>
<class>org.keycloak.models.sessions.jpa.entities.UsernameLoginFailureEntity</class>
diff --git a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.3.0.Beta1.xml b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.3.0.Beta1.xml
index c469581..c1b385a 100755
--- a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.3.0.Beta1.xml
+++ b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.3.0.Beta1.xml
@@ -129,6 +129,17 @@
<column name="REQUIRED_ACTION" value="UPDATE_PASSWORD"/>
<where>ACTION = 3</where>
</update>
+ <createTable tableName="CLIENT_USER_SESSION_NOTE">
+ <column name="NAME" type="VARCHAR(255)">
+ <constraints nullable="false"/>
+ </column>
+ <column name="VALUE" type="VARCHAR(255)"/>
+ <column name="CLIENT_SESSION" type="VARCHAR(36)">
+ <constraints nullable="false"/>
+ </column>
+ </createTable>
+ <addPrimaryKey columnNames="CLIENT_SESSION, NAME" constraintName="CONSTRAINT_CLIENT_USER_SESSION_NOTE" tableName="CLIENT_USER_SESSION_NOTE"/>
+ <addForeignKeyConstraint baseColumnNames="CLIENT_SESSION" baseTableName="CLIENT_USER_SESSION_NOTE" constraintName="FK_CLIENT_USER_SESSION_NOTE" referencedColumnNames="ID" referencedTableName="CLIENT_SESSION"/>
<addPrimaryKey columnNames="ID" constraintName="CONSTRAINT_AUTHENTICATOR_PK" tableName="AUTHENTICATOR"/>
<addPrimaryKey columnNames="ID" constraintName="CONSTRAINT_AUTHENTICATION_FLOW_PK" tableName="AUTHENTICATION_FLOW"/>
<addPrimaryKey columnNames="ID" constraintName="CONSTRAINT_AUTHENTICATION_EXECUTION_PK" tableName="AUTHENTICATION_EXECUTION"/>
diff --git a/events/api/src/main/java/org/keycloak/events/Details.java b/events/api/src/main/java/org/keycloak/events/Details.java
index cee7475..d1ef4a5 100755
--- a/events/api/src/main/java/org/keycloak/events/Details.java
+++ b/events/api/src/main/java/org/keycloak/events/Details.java
@@ -11,6 +11,7 @@ public interface Details {
String CODE_ID = "code_id";
String REDIRECT_URI = "redirect_uri";
String RESPONSE_TYPE = "response_type";
+ String AUTH_TYPE = "auth_type";
String AUTH_METHOD = "auth_method";
String IDENTITY_PROVIDER = "identity_provider";
String IDENTITY_PROVIDER_USERNAME = "identity_provider_identity";
diff --git a/model/api/src/main/java/org/keycloak/models/ClientSessionModel.java b/model/api/src/main/java/org/keycloak/models/ClientSessionModel.java
index 2c66df3..5d68237 100755
--- a/model/api/src/main/java/org/keycloak/models/ClientSessionModel.java
+++ b/model/api/src/main/java/org/keycloak/models/ClientSessionModel.java
@@ -52,6 +52,21 @@ public interface ClientSessionModel {
public void setNote(String name, String value);
public void removeNote(String name);
+ /**
+ * These are notes you want applied to the UserSessionModel when the client session is attached to it.
+ *
+ * @param name
+ * @param value
+ */
+ public void setUserSessionNote(String name, String value);
+
+ /**
+ * These are notes you want applied to the UserSessionModel when the client session is attached to it.
+ *
+ * @return
+ */
+ public Map<String, String> getUserSessionNotes();
+
public static enum Action {
OAUTH_GRANT,
CODE_TO_TOKEN,
diff --git a/model/api/src/main/java/org/keycloak/models/UserFederationManager.java b/model/api/src/main/java/org/keycloak/models/UserFederationManager.java
index bd07b77..d094b82 100755
--- a/model/api/src/main/java/org/keycloak/models/UserFederationManager.java
+++ b/model/api/src/main/java/org/keycloak/models/UserFederationManager.java
@@ -370,6 +370,25 @@ public class UserFederationManager implements UserProvider {
return session.userStorage().validCredentials(realm, user, input);
}
+ /**
+ * Is the user configured to use this credential type
+ *
+ * @return
+ */
+ public boolean configuredForCredentialType(String type, RealmModel realm, UserModel user) {
+ UserFederationProvider link = getFederationLink(realm, user);
+ if (link != null) {
+ Set<String> supportedCredentialTypes = link.getSupportedCredentialTypes(user);
+ if (supportedCredentialTypes.contains(type)) return true;
+ }
+ List<UserCredentialValueModel> creds = user.getCredentialsDirectly();
+ for (UserCredentialValueModel cred : creds) {
+ if (cred.getType().equals(type)) return true;
+ }
+ return false;
+ }
+
+
@Override
public boolean validCredentials(RealmModel realm, UserModel user, UserCredentialModel... input) {
return validCredentials(realm, user, Arrays.asList(input));
diff --git a/model/api/src/main/java/org/keycloak/models/UserModel.java b/model/api/src/main/java/org/keycloak/models/UserModel.java
index 2088abc..645250e 100755
--- a/model/api/src/main/java/org/keycloak/models/UserModel.java
+++ b/model/api/src/main/java/org/keycloak/models/UserModel.java
@@ -69,14 +69,6 @@ public interface UserModel {
void updateCredentialDirectly(UserCredentialValueModel cred);
- /**
- * Is the use configured to use this credential type
- *
- * @param type
- * @return
- */
- boolean configuredForCredentialType(String type);
-
Set<RoleModel> getRealmRoleMappings();
Set<RoleModel> getClientRoleMappings(ClientModel app);
boolean hasRole(RoleModel role);
diff --git a/model/api/src/main/java/org/keycloak/models/UserSessionModel.java b/model/api/src/main/java/org/keycloak/models/UserSessionModel.java
index 769fbca..6af29cf 100755
--- a/model/api/src/main/java/org/keycloak/models/UserSessionModel.java
+++ b/model/api/src/main/java/org/keycloak/models/UserSessionModel.java
@@ -38,10 +38,12 @@ public interface UserSessionModel {
List<ClientSessionModel> getClientSessions();
public static enum AuthenticatorStatus {
+ FAILED,
SUCCESS,
SETUP_REQUIRED,
ATTEMPTED,
- SKIPPED
+ SKIPPED,
+ CHALLENGED
}
public String getNote(String name);
diff --git a/model/api/src/main/java/org/keycloak/models/utils/DefaultAuthenticationFlows.java b/model/api/src/main/java/org/keycloak/models/utils/DefaultAuthenticationFlows.java
index 05a5a4a..af057b6 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/DefaultAuthenticationFlows.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/DefaultAuthenticationFlows.java
@@ -10,6 +10,10 @@ import org.keycloak.models.RealmModel;
* @version $Revision: 1 $
*/
public class DefaultAuthenticationFlows {
+
+ public static final String BROWSER_FLOW = "browser";
+ public static final String FORMS_FLOW = "forms";
+
public static void addFlows(RealmModel realm) {
AuthenticatorModel model = new AuthenticatorModel();
model.setProviderId("auth-cookie");
@@ -31,9 +35,13 @@ public class DefaultAuthenticationFlows {
model.setProviderId("auth-otp-form");
model.setAlias("Single OTP Form");
AuthenticatorModel otp = realm.addAuthenticator(model);
+ model = new AuthenticatorModel();
+ model.setProviderId("auth-spnego");
+ model.setAlias("Kerberos");
+ AuthenticatorModel kerberos = realm.addAuthenticator(model);
AuthenticationFlowModel browser = new AuthenticationFlowModel();
- browser.setAlias("browser");
+ browser.setAlias(BROWSER_FLOW);
browser.setDescription("browser based authentication");
browser = realm.addAuthenticationFlow(browser);
AuthenticationExecutionModel execution = new AuthenticationExecutionModel();
@@ -44,15 +52,23 @@ public class DefaultAuthenticationFlows {
execution.setUserSetupAllowed(false);
execution.setAutheticatorFlow(false);
realm.addAuthenticatorExecution(execution);
+ execution = new AuthenticationExecutionModel();
+ execution.setParentFlow(browser.getId());
+ execution.setRequirement(AuthenticationExecutionModel.Requirement.DISABLED);
+ execution.setAuthenticator(kerberos.getId());
+ execution.setPriority(1);
+ execution.setUserSetupAllowed(false);
+ execution.setAutheticatorFlow(false);
+ realm.addAuthenticatorExecution(execution);
AuthenticationFlowModel forms = new AuthenticationFlowModel();
- forms.setAlias("forms");
+ forms.setAlias(FORMS_FLOW);
forms.setDescription("Username, password, otp and other auth forms.");
forms = realm.addAuthenticationFlow(forms);
execution = new AuthenticationExecutionModel();
execution.setParentFlow(browser.getId());
execution.setRequirement(AuthenticationExecutionModel.Requirement.ALTERNATIVE);
execution.setAuthenticator(forms.getId());
- execution.setPriority(1);
+ execution.setPriority(2);
execution.setUserSetupAllowed(false);
execution.setAutheticatorFlow(true);
realm.addAuthenticatorExecution(execution);
diff --git a/model/api/src/main/java/org/keycloak/models/utils/UserModelDelegate.java b/model/api/src/main/java/org/keycloak/models/utils/UserModelDelegate.java
index 3f6bec4..7123c3e 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/UserModelDelegate.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/UserModelDelegate.java
@@ -88,11 +88,6 @@ public class UserModelDelegate implements UserModel {
}
@Override
- public boolean configuredForCredentialType(String type) {
- return delegate.configuredForCredentialType(type);
- }
-
- @Override
public void addRequiredAction(RequiredAction action) {
delegate.addRequiredAction(action);
}
diff --git a/model/file/src/main/java/org/keycloak/models/file/adapter/UserAdapter.java b/model/file/src/main/java/org/keycloak/models/file/adapter/UserAdapter.java
index b39fc6d..39024c1 100755
--- a/model/file/src/main/java/org/keycloak/models/file/adapter/UserAdapter.java
+++ b/model/file/src/main/java/org/keycloak/models/file/adapter/UserAdapter.java
@@ -221,17 +221,6 @@ public class UserAdapter implements UserModel, Comparable {
}
@Override
- public boolean configuredForCredentialType(String type) {
- List<UserCredentialValueModel> creds = getCredentialsDirectly();
- for (UserCredentialValueModel cred : creds) {
- if (cred.getType().equals(type)) return true;
- }
- return false;
- }
-
-
-
- @Override
public boolean isTotp() {
return user.isTotp();
}
diff --git a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/UserAdapter.java b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/UserAdapter.java
index dc159ce..aa80a25 100755
--- a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/UserAdapter.java
+++ b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/UserAdapter.java
@@ -132,15 +132,6 @@ public class UserAdapter implements UserModel {
}
@Override
- public boolean configuredForCredentialType(String type) {
- List<UserCredentialValueModel> creds = getCredentialsDirectly();
- for (UserCredentialValueModel cred : creds) {
- if (cred.getType().equals(type)) return true;
- }
- return false;
- }
-
- @Override
public String getFirstName() {
if (updated != null) return updated.getFirstName();
return cached.getFirstName();
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java
index 977a4f5..670f5f0 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java
@@ -186,15 +186,6 @@ public class UserAdapter implements UserModel {
}
@Override
- public boolean configuredForCredentialType(String type) {
- List<UserCredentialValueModel> creds = getCredentialsDirectly();
- for (UserCredentialValueModel cred : creds) {
- if (cred.getType().equals(type)) return true;
- }
- return false;
- }
-
- @Override
public String getFirstName() {
return user.getFirstName();
}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java
index 813faab..79a6260 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java
@@ -334,16 +334,6 @@ public class UserAdapter extends AbstractMongoAdapter<MongoUserEntity> implement
}
@Override
- public boolean configuredForCredentialType(String type) {
- List<UserCredentialValueModel> creds = getCredentialsDirectly();
- for (UserCredentialValueModel cred : creds) {
- if (cred.getType().equals(type)) return true;
- }
- return false;
- }
-
-
- @Override
public void updateCredentialDirectly(UserCredentialValueModel credModel) {
CredentialEntity credentialEntity = getCredentialEntity(user, credModel.getType());
diff --git a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/ClientSessionAdapter.java b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/ClientSessionAdapter.java
index ddf42d5..226a2ec 100755
--- a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/ClientSessionAdapter.java
+++ b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/ClientSessionAdapter.java
@@ -10,6 +10,7 @@ import org.keycloak.models.UserSessionModel;
import org.keycloak.models.sessions.infinispan.entities.ClientSessionEntity;
import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
+import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
@@ -164,6 +165,26 @@ public class ClientSessionAdapter implements ClientSessionModel {
}
}
+ @Override
+ public void setUserSessionNote(String name, String value) {
+ if (entity.getUserSessionNotes() == null) {
+ entity.setUserSessionNotes(new HashMap<String, String>());
+ }
+ entity.getNotes().put(name, value);
+ update();
+
+ }
+
+ @Override
+ public Map<String, String> getUserSessionNotes() {
+ if (entity.getUserSessionNotes() == null) {
+ return Collections.EMPTY_MAP;
+ }
+ HashMap<String, String> copy = new HashMap<>();
+ copy.putAll(entity.getUserSessionNotes());
+ return copy;
+ }
+
void update() {
provider.getTx().replace(cache, entity.getId(), entity);
}
diff --git a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/ClientSessionEntity.java b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/ClientSessionEntity.java
index cc8ce20..53c8218 100755
--- a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/ClientSessionEntity.java
+++ b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/ClientSessionEntity.java
@@ -29,6 +29,7 @@ public class ClientSessionEntity extends SessionEntity {
private Set<String> roles;
private Set<String> protocolMappers;
private Map<String, String> notes;
+ private Map<String, String> userSessionNotes;
private Map<String, UserSessionModel.AuthenticatorStatus> authenticatorStatus = new HashMap<>();
private String authUserId;
@@ -127,4 +128,12 @@ public class ClientSessionEntity extends SessionEntity {
public void setAuthUserId(String authUserId) {
this.authUserId = authUserId;
}
+
+ public Map<String, String> getUserSessionNotes() {
+ return userSessionNotes;
+ }
+
+ public void setUserSessionNotes(Map<String, String> userSessionNotes) {
+ this.userSessionNotes = userSessionNotes;
+ }
}
diff --git a/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/ClientSessionAdapter.java b/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/ClientSessionAdapter.java
index 68e3b53..0ce21fa 100755
--- a/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/ClientSessionAdapter.java
+++ b/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/ClientSessionAdapter.java
@@ -10,9 +10,11 @@ import org.keycloak.models.sessions.jpa.entities.ClientSessionEntity;
import org.keycloak.models.sessions.jpa.entities.ClientSessionNoteEntity;
import org.keycloak.models.sessions.jpa.entities.ClientSessionProtocolMapperEntity;
import org.keycloak.models.sessions.jpa.entities.ClientSessionRoleEntity;
+import org.keycloak.models.sessions.jpa.entities.ClientUserSessionNoteEntity;
import org.keycloak.models.sessions.jpa.entities.UserSessionEntity;
import javax.persistence.EntityManager;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
@@ -79,6 +81,32 @@ public class ClientSessionAdapter implements ClientSessionModel {
}
@Override
+ public void setUserSessionNote(String name, String value) {
+ for (ClientUserSessionNoteEntity attr : entity.getUserSessionNotes()) {
+ if (attr.getName().equals(name)) {
+ attr.setValue(value);
+ return;
+ }
+ }
+ ClientUserSessionNoteEntity attr = new ClientUserSessionNoteEntity();
+ attr.setName(name);
+ attr.setValue(value);
+ attr.setClientSession(entity);
+ em.persist(attr);
+ entity.getUserSessionNotes().add(attr);
+
+ }
+
+ @Override
+ public Map<String, String> getUserSessionNotes() {
+ Map<String, String> copy = new HashMap<>();
+ for (ClientUserSessionNoteEntity attr : entity.getUserSessionNotes()) {
+ copy.put(attr.getName(), attr.getValue());
+ }
+ return copy;
+ }
+
+ @Override
public String getId() {
return entity.getId();
}
diff --git a/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/entities/ClientSessionEntity.java b/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/entities/ClientSessionEntity.java
index 7cc9062..bf0c520 100755
--- a/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/entities/ClientSessionEntity.java
+++ b/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/entities/ClientSessionEntity.java
@@ -70,6 +70,9 @@ public class ClientSessionEntity {
protected Collection<ClientSessionNoteEntity> notes = new ArrayList<ClientSessionNoteEntity>();
@OneToMany(cascade = CascadeType.REMOVE, orphanRemoval = true, mappedBy="clientSession")
+ protected Collection<ClientUserSessionNoteEntity> userSessionNotes = new ArrayList<>();
+
+ @OneToMany(cascade = CascadeType.REMOVE, orphanRemoval = true, mappedBy="clientSession")
protected Collection<ClientSessionAuthStatusEntity> authanticatorStatus = new ArrayList<>();
public String getId() {
@@ -175,4 +178,12 @@ public class ClientSessionEntity {
public void setUserId(String userId) {
this.userId = userId;
}
+
+ public Collection<ClientUserSessionNoteEntity> getUserSessionNotes() {
+ return userSessionNotes;
+ }
+
+ public void setUserSessionNotes(Collection<ClientUserSessionNoteEntity> userSessionNotes) {
+ this.userSessionNotes = userSessionNotes;
+ }
}
diff --git a/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/entities/ClientUserSessionNoteEntity.java b/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/entities/ClientUserSessionNoteEntity.java
new file mode 100755
index 0000000..9051925
--- /dev/null
+++ b/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/entities/ClientUserSessionNoteEntity.java
@@ -0,0 +1,109 @@
+package org.keycloak.models.sessions.jpa.entities;
+
+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 = "removeClientUserSessionNoteByUser", query="delete from ClientUserSessionNoteEntity 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 = "removeClientUserSessionNoteByClient", query="delete from ClientUserSessionNoteEntity r where r.clientSession IN (select c from ClientSessionEntity c where c.clientId = :clientId and c.realmId = :realmId)"),
+ @NamedQuery(name = "removeClientUserSessionNoteByRealm", query="delete from ClientUserSessionNoteEntity r where r.clientSession IN (select c from ClientSessionEntity c where c.realmId = :realmId)"),
+ @NamedQuery(name = "removeClientUserSessionNoteByExpired", query = "delete from ClientUserSessionNoteEntity 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 = "removeDetachedUserClientSessionNoteByExpired", query = "delete from ClientUserSessionNoteEntity 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_USER_SESSION_NOTE")
+@Entity
+@IdClass(ClientUserSessionNoteEntity.Key.class)
+public class ClientUserSessionNoteEntity {
+
+ @Id
+ @ManyToOne(fetch= FetchType.LAZY)
+ @JoinColumn(name = "CLIENT_SESSION")
+ protected ClientSessionEntity clientSession;
+
+ @Id
+ @Column(name = "NAME")
+ protected String name;
+ @Column(name = "VALUE")
+ protected String value;
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ public void setValue(String value) {
+ this.value = value;
+ }
+
+ public ClientSessionEntity getClientSession() {
+ return clientSession;
+ }
+
+ public void setClientSession(ClientSessionEntity clientSession) {
+ this.clientSession = clientSession;
+ }
+
+ public static class Key implements Serializable {
+
+ protected ClientSessionEntity clientSession;
+
+ protected String name;
+
+ public Key() {
+ }
+
+ public Key(ClientSessionEntity clientSession, String name) {
+ this.clientSession = clientSession;
+ this.name = name;
+ }
+
+ public ClientSessionEntity getClientSession() {
+ return clientSession;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ Key key = (Key) o;
+
+ if (name != null ? !name.equals(key.name) : key.name != 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 + (name != null ? name.hashCode() : 0);
+ return result;
+ }
+ }
+
+}
diff --git a/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/ClientSessionAdapter.java b/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/ClientSessionAdapter.java
index cff4ca3..cbea1d6 100755
--- a/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/ClientSessionAdapter.java
+++ b/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/ClientSessionAdapter.java
@@ -135,6 +135,16 @@ public class ClientSessionAdapter implements ClientSessionModel {
}
@Override
+ public void setUserSessionNote(String name, String value) {
+ entity.getUserSessionNotes().put(name, value);
+ }
+
+ @Override
+ public Map<String, String> getUserSessionNotes() {
+ return entity.getUserSessionNotes();
+ }
+
+ @Override
public String getAuthMethod() {
return entity.getAuthMethod();
}
diff --git a/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/entities/ClientSessionEntity.java b/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/entities/ClientSessionEntity.java
index 9823570..bd6eac9 100755
--- a/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/entities/ClientSessionEntity.java
+++ b/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/entities/ClientSessionEntity.java
@@ -28,6 +28,7 @@ public class ClientSessionEntity {
private Set<String> roles;
private Set<String> protocolMappers;
private Map<String, String> notes = new HashMap<>();
+ private Map<String, String> userSessionNotes = new HashMap<>();
public String getId() {
return id;
@@ -128,4 +129,8 @@ public class ClientSessionEntity {
public void setAuthenticatorStatus(Map<String, UserSessionModel.AuthenticatorStatus> authenticatorStatus) {
this.authenticatorStatus = authenticatorStatus;
}
+
+ public Map<String, String> getUserSessionNotes() {
+ return userSessionNotes;
+ }
}
diff --git a/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/ClientSessionAdapter.java b/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/ClientSessionAdapter.java
index e5fd346..52fa0aa 100755
--- a/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/ClientSessionAdapter.java
+++ b/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/ClientSessionAdapter.java
@@ -10,6 +10,7 @@ import org.keycloak.models.UserSessionModel;
import org.keycloak.models.sessions.mongo.entities.MongoClientSessionEntity;
import org.keycloak.models.sessions.mongo.entities.MongoUserSessionEntity;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
@@ -157,6 +158,19 @@ public class ClientSessionAdapter extends AbstractMongoAdapter<MongoClientSessio
}
@Override
+ public void setUserSessionNote(String name, String value) {
+ entity.getUserSessionNotes().put(name, value);
+ updateMongoEntity();
+ }
+
+ @Override
+ public Map<String, String> getUserSessionNotes() {
+ Map<String, String> copy = new HashMap<>();
+ copy.putAll(entity.getUserSessionNotes());
+ return copy;
+ }
+
+ @Override
public Map<String, UserSessionModel.AuthenticatorStatus> getAuthenticators() {
return entity.getAuthenticatorStatus();
}
diff --git a/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/entities/MongoClientSessionEntity.java b/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/entities/MongoClientSessionEntity.java
index ed8099d..10a211f 100755
--- a/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/entities/MongoClientSessionEntity.java
+++ b/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/entities/MongoClientSessionEntity.java
@@ -30,6 +30,7 @@ public class MongoClientSessionEntity extends AbstractIdentifiableEntity impleme
private List<String> roles;
private List<String> protocolMappers;
private Map<String, String> notes = new HashMap<String, String>();
+ private Map<String, String> userSessionNotes = new HashMap<String, String>();
private Map<String, UserSessionModel.AuthenticatorStatus> authenticatorStatus = new HashMap<>();
private String authUserId;
@@ -113,6 +114,14 @@ public class MongoClientSessionEntity extends AbstractIdentifiableEntity impleme
this.notes = notes;
}
+ public Map<String, String> getUserSessionNotes() {
+ return userSessionNotes;
+ }
+
+ public void setUserSessionNotes(Map<String, String> userSessionNotes) {
+ this.userSessionNotes = userSessionNotes;
+ }
+
public String getSessionId() {
return sessionId;
}
diff --git a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlService.java b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlService.java
index d11ffb0..dbbde29 100755
--- a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlService.java
+++ b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlService.java
@@ -6,6 +6,7 @@ import org.jboss.resteasy.spi.HttpRequest;
import org.jboss.resteasy.spi.HttpResponse;
import org.keycloak.ClientConnection;
import org.keycloak.VerificationException;
+import org.keycloak.authentication.AuthenticationProcessor;
import org.keycloak.dom.saml.v2.SAML2Object;
import org.keycloak.dom.saml.v2.protocol.AuthnRequestType;
import org.keycloak.dom.saml.v2.protocol.LogoutRequestType;
@@ -17,6 +18,7 @@ import org.keycloak.events.Errors;
import org.keycloak.events.EventBuilder;
import org.keycloak.events.EventType;
import org.keycloak.login.LoginFormsProvider;
+import org.keycloak.models.AuthenticationFlowModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.KeycloakSession;
@@ -282,6 +284,10 @@ public class SamlService {
}
}
+ return newBrowserAuthentication(clientSession);
+ }
+
+ private Response oldBrowserAuthentication(ClientSessionModel clientSession) {
Response response = authManager.checkNonFormAuthentication(session, clientSession, realm, uriInfo, request, clientConnection, headers, event);
if (response != null) return response;
@@ -311,6 +317,29 @@ public class SamlService {
return forms.createLogin();
}
+ protected Response newBrowserAuthentication(ClientSessionModel clientSession) {
+ String flowId = null;
+ for (AuthenticationFlowModel flow : realm.getAuthenticationFlows()) {
+ if (flow.getAlias().equals("browser")) {
+ flowId = flow.getId();
+ break;
+ }
+ }
+ AuthenticationProcessor processor = new AuthenticationProcessor();
+ processor.setClientSession(clientSession)
+ .setFlowId(flowId)
+ .setConnection(clientConnection)
+ .setEventBuilder(event)
+ .setProtector(authManager.getProtector())
+ .setRealm(realm)
+ .setSession(session)
+ .setUriInfo(uriInfo)
+ .setRequest(request);
+
+ return processor.authenticate();
+ }
+
+
private String getBindingType(AuthnRequestType requestAbstractType) {
URI requestedProtocolBinding = requestAbstractType.getProtocolBinding();
diff --git a/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java b/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
index 1ebfa50..570b9ca 100755
--- a/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
+++ b/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
@@ -3,7 +3,9 @@ package org.keycloak.authentication;
import org.jboss.logging.Logger;
import org.jboss.resteasy.spi.HttpRequest;
import org.keycloak.ClientConnection;
+import org.keycloak.events.Details;
import org.keycloak.events.EventBuilder;
+import org.keycloak.events.EventType;
import org.keycloak.models.AuthenticationExecutionModel;
import org.keycloak.models.AuthenticationFlowModel;
import org.keycloak.models.AuthenticatorModel;
@@ -19,7 +21,6 @@ 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>
@@ -34,7 +35,7 @@ public class AuthenticationProcessor {
protected UriInfo uriInfo;
protected KeycloakSession session;
protected BruteForceProtector protector;
- protected EventBuilder eventBuilder;
+ protected EventBuilder event;
protected HttpRequest request;
protected String flowId;
@@ -109,7 +110,7 @@ public class AuthenticationProcessor {
}
public AuthenticationProcessor setEventBuilder(EventBuilder eventBuilder) {
- this.eventBuilder = eventBuilder;
+ this.event = eventBuilder;
return this;
}
@@ -125,23 +126,35 @@ public class AuthenticationProcessor {
private class Result implements AuthenticatorContext {
AuthenticatorModel model;
+ AuthenticationExecutionModel execution;
Authenticator authenticator;
Status status;
Response challenge;
Error error;
- private Result(AuthenticatorModel model, Authenticator authenticator) {
+ private Result(AuthenticationExecutionModel execution, AuthenticatorModel model, Authenticator authenticator) {
+ this.execution = execution;
this.model = model;
this.authenticator = authenticator;
}
@Override
- public AuthenticatorModel getModel() {
+ public AuthenticationExecutionModel getExecution() {
+ return execution;
+ }
+
+ @Override
+ public void setExecution(AuthenticationExecutionModel execution) {
+ this.execution = execution;
+ }
+
+ @Override
+ public AuthenticatorModel getAuthenticatorModel() {
return model;
}
@Override
- public void setModel(AuthenticatorModel model) {
+ public void setAuthenticatorModel(AuthenticatorModel model) {
this.model = model;
}
@@ -251,6 +264,11 @@ public class AuthenticationProcessor {
public BruteForceProtector getProtector() {
return AuthenticationProcessor.this.protector;
}
+
+ @Override
+ public EventBuilder getEvent() {
+ return AuthenticationProcessor.this.event;
+ }
}
public static class AuthException extends RuntimeException {
@@ -305,6 +323,14 @@ public class AuthenticationProcessor {
}
public Response authenticate() throws AuthException {
+ event.event(EventType.LOGIN);
+ event.client(clientSession.getClient().getClientId())
+ .detail(Details.REDIRECT_URI, clientSession.getRedirectUri())
+ .detail(Details.AUTH_METHOD, clientSession.getAuthMethod());
+ String authType = clientSession.getNote(Details.AUTH_TYPE);
+ if (authType != null) {
+ event.detail(Details.AUTH_TYPE, authType);
+ }
UserModel authUser = clientSession.getAuthenticatedUser();
validateUser(authUser);
Response challenge = processFlow(flowId);
@@ -325,6 +351,7 @@ public class AuthenticationProcessor {
List<AuthenticationExecutionModel> executions = realm.getAuthenticationExecutions(flowId);
if (executions == null) return null;
Response alternativeChallenge = null;
+ AuthenticationExecutionModel challengedAlternativeExecution = null;
boolean alternativeSuccessful = false;
for (AuthenticationExecutionModel model : executions) {
if (isProcessed(model)) {
@@ -354,23 +381,31 @@ public class AuthenticationProcessor {
UserModel authUser = clientSession.getAuthenticatedUser();
if (authenticator.requiresUser() && authUser == null){
- if (alternativeChallenge != null) return alternativeChallenge;
+ if (alternativeChallenge != null) {
+ clientSession.setAuthenticatorStatus(challengedAlternativeExecution.getId(), UserSessionModel.AuthenticatorStatus.CHALLENGED);
+ return alternativeChallenge;
+ }
throw new AuthException(Error.UNKNOWN_USER);
}
-
- if (authenticator.requiresUser() && authUser != null && !authenticator.configuredFor(authUser)) {
- if (model.getRequirement() == AuthenticationExecutionModel.Requirement.REQUIRED) {
- if (model.isUserSetupAllowed()) {
- clientSession.setAuthenticatorStatus(model.getId(), UserSessionModel.AuthenticatorStatus.SETUP_REQUIRED);
- authUser.addRequiredAction(authenticator.getRequiredAction());
-
- } else {
- throw new AuthException(Error.CREDENTIAL_SETUP_REQUIRED);
+ boolean configuredFor = false;
+ if (authenticator.requiresUser() && authUser != null) {
+ configuredFor = authenticator.configuredFor(session, realm, authUser);
+ if (!configuredFor) {
+ if (model.getRequirement() == AuthenticationExecutionModel.Requirement.REQUIRED) {
+ if (model.isUserSetupAllowed()) {
+ clientSession.setAuthenticatorStatus(model.getId(), UserSessionModel.AuthenticatorStatus.SETUP_REQUIRED);
+ String requiredAction = authenticator.getRequiredAction();
+ if (!authUser.getRequiredActions().contains(requiredAction)) {
+ authUser.addRequiredAction(requiredAction);
+ }
+ continue;
+ } else {
+ throw new AuthException(Error.CREDENTIAL_SETUP_REQUIRED);
+ }
}
}
- continue;
}
- context = new Result(authenticatorModel, authenticator);
+ context = new Result(model, authenticatorModel, authenticator);
authenticator.authenticate(context);
Status result = context.getStatus();
if (result == Status.SUCCESS){
@@ -379,15 +414,24 @@ public class AuthenticationProcessor {
continue;
} else if (result == Status.FAILED) {
logUserFailure();
+ clientSession.setAuthenticatorStatus(model.getId(), UserSessionModel.AuthenticatorStatus.FAILED);
if (context.challenge != null) return context.challenge;
throw new AuthException(context.error);
} else if (result == Status.CHALLENGE) {
- if (model.isRequired()) return context.challenge;
- else if (model.isAlternative()) alternativeChallenge = context.challenge;
- else clientSession.setAuthenticatorStatus(model.getId(), UserSessionModel.AuthenticatorStatus.SKIPPED);
+ if (model.isRequired() || (model.isOptional() && configuredFor)) {
+ clientSession.setAuthenticatorStatus(model.getId(), UserSessionModel.AuthenticatorStatus.CHALLENGED);
+ return context.challenge;
+ }
+ else if (model.isAlternative()) {
+ alternativeChallenge = context.challenge;
+ challengedAlternativeExecution = model;
+ } else {
+ clientSession.setAuthenticatorStatus(model.getId(), UserSessionModel.AuthenticatorStatus.SKIPPED);
+ }
continue;
} else if (result == Status.FAILURE_CHALLENGE) {
logUserFailure();
+ clientSession.setAuthenticatorStatus(model.getId(), UserSessionModel.AuthenticatorStatus.CHALLENGED);
return context.challenge;
} else if (result == Status.ATTEMPTED) {
if (model.getRequirement() == AuthenticationExecutionModel.Requirement.REQUIRED) throw new AuthException(Error.INVALID_CREDENTIALS);
@@ -415,17 +459,22 @@ public class AuthenticationProcessor {
}
protected Response authenticationComplete() {
+ String username = clientSession.getAuthenticatedUser().getUsername();
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 = session.sessions().createUserSession(realm, clientSession.getAuthenticatedUser(), username, connection.getRemoteAddr(), "form", false, null, null);
userSession.setState(UserSessionModel.State.LOGGING_IN);
}
TokenManager.attachClientSession(userSession, clientSession);
+ event.user(userSession.getUser())
+ .detail(Details.USERNAME, username)
+ .session(userSession);
+
return processRequiredActions();
}
public Response processRequiredActions() {
- return AuthenticationManager.nextActionAfterAuthentication(session, userSession, clientSession, connection, request, uriInfo, eventBuilder);
+ return AuthenticationManager.nextActionAfterAuthentication(session, userSession, clientSession, connection, request, uriInfo, event);
}
diff --git a/services/src/main/java/org/keycloak/authentication/Authenticator.java b/services/src/main/java/org/keycloak/authentication/Authenticator.java
index 3d5c64e..ee9d435 100755
--- a/services/src/main/java/org/keycloak/authentication/Authenticator.java
+++ b/services/src/main/java/org/keycloak/authentication/Authenticator.java
@@ -1,5 +1,7 @@
package org.keycloak.authentication;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.provider.Provider;
@@ -10,7 +12,7 @@ import org.keycloak.provider.Provider;
public interface Authenticator extends Provider {
boolean requiresUser();
void authenticate(AuthenticatorContext context);
- boolean configuredFor(UserModel user);
+ boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user);
String getRequiredAction();
diff --git a/services/src/main/java/org/keycloak/authentication/AuthenticatorContext.java b/services/src/main/java/org/keycloak/authentication/AuthenticatorContext.java
index 8a91d5e..91e9514 100755
--- a/services/src/main/java/org/keycloak/authentication/AuthenticatorContext.java
+++ b/services/src/main/java/org/keycloak/authentication/AuthenticatorContext.java
@@ -2,6 +2,8 @@ package org.keycloak.authentication;
import org.jboss.resteasy.spi.HttpRequest;
import org.keycloak.ClientConnection;
+import org.keycloak.events.EventBuilder;
+import org.keycloak.models.AuthenticationExecutionModel;
import org.keycloak.models.AuthenticatorModel;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.KeycloakSession;
@@ -19,9 +21,15 @@ import javax.ws.rs.core.UriInfo;
* @version $Revision: 1 $
*/
public interface AuthenticatorContext {
- AuthenticatorModel getModel();
+ EventBuilder getEvent();
- void setModel(AuthenticatorModel model);
+ AuthenticationExecutionModel getExecution();
+
+ void setExecution(AuthenticationExecutionModel execution);
+
+ AuthenticatorModel getAuthenticatorModel();
+
+ void setAuthenticatorModel(AuthenticatorModel model);
Authenticator getAuthenticator();
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/AbstractFormAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/AbstractFormAuthenticator.java
new file mode 100755
index 0000000..0657e8a
--- /dev/null
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/AbstractFormAuthenticator.java
@@ -0,0 +1,80 @@
+package org.keycloak.authentication.authenticators;
+
+import org.keycloak.OAuth2Constants;
+import org.keycloak.authentication.AuthenticationProcessor;
+import org.keycloak.authentication.AuthenticatorContext;
+import org.keycloak.events.Errors;
+import org.keycloak.login.LoginFormsProvider;
+import org.keycloak.models.ClientSessionModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.services.managers.ClientSessionCode;
+import org.keycloak.services.messages.Messages;
+import org.keycloak.services.resources.LoginActionsService;
+
+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 AbstractFormAuthenticator {
+ protected boolean isActionUrl(AuthenticatorContext context) {
+ URI expected = LoginActionsService.authenticationFormProcessor(context.getUriInfo()).build(context.getRealm().getName());
+ String current = context.getUriInfo().getAbsolutePath().getPath();
+ String expectedPath = expected.getPath();
+ return expectedPath.equals(current);
+
+ }
+
+ protected LoginFormsProvider loginForm(AuthenticatorContext context) {
+ ClientSessionCode code = new ClientSessionCode(context.getRealm(), context.getClientSession());
+ code.setAction(ClientSessionModel.Action.AUTHENTICATE);
+ URI action = getActionUrl(context, code);
+ return context.getSession().getProvider(LoginFormsProvider.class)
+ .setActionUri(action)
+ .setClientSessionCode(code.getCode());
+ }
+
+ public static URI getActionUrl(AuthenticatorContext context, ClientSessionCode code) {
+ return LoginActionsService.authenticationFormProcessor(context.getUriInfo())
+ .queryParam(OAuth2Constants.CODE, code.getCode())
+ .build(context.getRealm().getName());
+ }
+
+ protected Response invalidUser(AuthenticatorContext context) {
+ return loginForm(context).setError(Messages.INVALID_USER).createLogin();
+ }
+
+ protected Response disabledUser(AuthenticatorContext context) {
+ return loginForm(context).setError(Messages.ACCOUNT_DISABLED).createLogin();
+ }
+
+ protected Response temporarilyDisabledUser(AuthenticatorContext context) {
+ return loginForm(context).setError(Messages.ACCOUNT_TEMPORARILY_DISABLED).createLogin();
+ }
+
+ public boolean invalidUser(AuthenticatorContext context, UserModel user) {
+ if (user == null) {
+ context.getEvent().error(Errors.USER_NOT_FOUND);
+ Response challengeResponse = invalidUser(context);
+ context.failureChallenge(AuthenticationProcessor.Error.INVALID_USER, challengeResponse);
+ return true;
+ }
+ if (!user.isEnabled()) {
+ context.getEvent().error(Errors.USER_DISABLED);
+ Response challengeResponse = disabledUser(context);
+ context.failureChallenge(AuthenticationProcessor.Error.USER_DISABLED, challengeResponse);
+ return true;
+ }
+ if (context.getRealm().isBruteForceProtected()) {
+ if (context.getProtector().isTemporarilyDisabled(context.getSession(), context.getRealm(), user.getUsername())) {
+ context.getEvent().error(Errors.USER_TEMPORARILY_DISABLED);
+ Response challengeResponse = temporarilyDisabledUser(context);
+ context.failureChallenge(AuthenticationProcessor.Error.USER_TEMPORARILY_DISABLED, challengeResponse);
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/CookieAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/CookieAuthenticator.java
index b508024..1455b2f 100755
--- a/services/src/main/java/org/keycloak/authentication/authenticators/CookieAuthenticator.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/CookieAuthenticator.java
@@ -2,6 +2,8 @@ package org.keycloak.authentication.authenticators;
import org.keycloak.authentication.Authenticator;
import org.keycloak.authentication.AuthenticatorContext;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.services.managers.AuthenticationManager;
@@ -31,7 +33,7 @@ public class CookieAuthenticator implements Authenticator {
}
@Override
- public boolean configuredFor(UserModel user) {
+ public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) {
return true;
}
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/LoginFormOTPAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/LoginFormOTPAuthenticator.java
index c678c42..a750158 100755
--- a/services/src/main/java/org/keycloak/authentication/authenticators/LoginFormOTPAuthenticator.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/LoginFormOTPAuthenticator.java
@@ -3,6 +3,8 @@ package org.keycloak.authentication.authenticators;
import org.keycloak.authentication.AuthenticationProcessor;
import org.keycloak.authentication.AuthenticatorContext;
import org.keycloak.models.AuthenticatorModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserModel;
import org.keycloak.representations.idm.CredentialRepresentation;
@@ -61,8 +63,8 @@ public class LoginFormOTPAuthenticator extends LoginFormUsernameAuthenticator {
}
@Override
- public boolean configuredFor(UserModel user) {
- return user.configuredForCredentialType(UserCredentialModel.TOTP);
+ public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) {
+ return session.users().configuredForCredentialType(UserCredentialModel.TOTP, realm, user) && user.isTotp();
}
@Override
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/LoginFormPasswordAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/LoginFormPasswordAuthenticator.java
index 17419a1..98f532a 100755
--- a/services/src/main/java/org/keycloak/authentication/authenticators/LoginFormPasswordAuthenticator.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/LoginFormPasswordAuthenticator.java
@@ -2,7 +2,10 @@ package org.keycloak.authentication.authenticators;
import org.keycloak.authentication.AuthenticationProcessor;
import org.keycloak.authentication.AuthenticatorContext;
+import org.keycloak.events.Errors;
import org.keycloak.models.AuthenticatorModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserModel;
import org.keycloak.representations.idm.CredentialRepresentation;
@@ -42,6 +45,7 @@ public class LoginFormPasswordAuthenticator extends LoginFormUsernameAuthenticat
List<UserCredentialModel> credentials = new LinkedList<>();
String password = inputData.getFirst(CredentialRepresentation.PASSWORD);
if (password == null) {
+ context.getEvent().error(Errors.INVALID_USER_CREDENTIALS);
Response challengeResponse = badPassword(context);
context.failureChallenge(AuthenticationProcessor.Error.INVALID_CREDENTIALS, challengeResponse);
return;
@@ -49,6 +53,7 @@ public class LoginFormPasswordAuthenticator extends LoginFormUsernameAuthenticat
credentials.add(UserCredentialModel.password(password));
boolean valid = context.getSession().users().validCredentials(context.getRealm(), context.getUser(), credentials);
if (!valid) {
+ context.getEvent().error(Errors.INVALID_USER_CREDENTIALS);
Response challengeResponse = badPassword(context);
context.failureChallenge(AuthenticationProcessor.Error.INVALID_CREDENTIALS, challengeResponse);
return;
@@ -62,8 +67,8 @@ public class LoginFormPasswordAuthenticator extends LoginFormUsernameAuthenticat
}
@Override
- public boolean configuredFor(UserModel user) {
- return user.configuredForCredentialType(UserCredentialModel.PASSWORD);
+ public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) {
+ return session.users().configuredForCredentialType(UserCredentialModel.PASSWORD, realm, user);
}
@Override
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/LoginFormUsernameAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/LoginFormUsernameAuthenticator.java
index 56707f9..14ae6a1 100755
--- a/services/src/main/java/org/keycloak/authentication/authenticators/LoginFormUsernameAuthenticator.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/LoginFormUsernameAuthenticator.java
@@ -1,30 +1,29 @@
package org.keycloak.authentication.authenticators;
import org.jboss.resteasy.specimpl.MultivaluedMapImpl;
-import org.keycloak.OAuth2Constants;
import org.keycloak.authentication.AuthenticationProcessor;
import org.keycloak.authentication.Authenticator;
import org.keycloak.authentication.AuthenticatorContext;
+import org.keycloak.events.Details;
+import org.keycloak.events.Errors;
import org.keycloak.login.LoginFormsProvider;
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.utils.KeycloakModelUtils;
+import org.keycloak.protocol.LoginProtocol;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.services.managers.AuthenticationManager;
-import org.keycloak.services.managers.ClientSessionCode;
-import org.keycloak.services.messages.Messages;
-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 {
+public class LoginFormUsernameAuthenticator extends AbstractFormAuthenticator implements Authenticator {
protected AuthenticatorModel model;
public LoginFormUsernameAuthenticator(AuthenticatorModel model) {
@@ -47,15 +46,19 @@ public class LoginFormUsernameAuthenticator implements Authenticator {
context.challenge(challengeResponse);
return;
}
- validateUser(context);
- }
-
- protected boolean isActionUrl(AuthenticatorContext context) {
- URI expected = LoginActionsService.authenticationFormProcessor(context.getUriInfo()).build(context.getRealm().getName());
- String current = context.getUriInfo().getAbsolutePath().getPath();
- String expectedPath = expected.getPath();
- return expectedPath.equals(current);
+ MultivaluedMap<String, String> formData = context.getHttpRequest().getDecodedFormParameters();
+ if (formData.containsKey("cancel")) {
+ context.getEvent().error(Errors.REJECTED_BY_USER);
+ LoginProtocol protocol = context.getSession().getProvider(LoginProtocol.class, context.getClientSession().getAuthMethod());
+ protocol.setRealm(context.getRealm())
+ .setHttpHeaders(context.getHttpRequest().getHttpHeaders())
+ .setUriInfo(context.getUriInfo());
+ Response response = protocol.cancelLogin(context.getClientSession());
+ context.challenge(response);
+ return;
+ }
+ validateUser(context, formData);
}
@Override
@@ -71,71 +74,24 @@ public class LoginFormUsernameAuthenticator implements Authenticator {
return forms.createLogin();
}
- protected LoginFormsProvider loginForm(AuthenticatorContext context) {
- ClientSessionCode code = new ClientSessionCode(context.getRealm(), context.getClientSession());
- code.setAction(ClientSessionModel.Action.AUTHENTICATE);
- URI action = LoginActionsService.authenticationFormProcessor(context.getUriInfo())
- .queryParam(OAuth2Constants.CODE, code.getCode())
- .build(context.getRealm().getName());
- return context.getSession().getProvider(LoginFormsProvider.class)
- .setActionUri(action)
- .setClientSessionCode(code.getCode());
- }
-
- protected Response invalidUser(AuthenticatorContext context) {
- return loginForm(context).setError(Messages.INVALID_USER).createLogin();
- }
-
- protected Response disabledUser(AuthenticatorContext context) {
- return loginForm(context).setError(Messages.ACCOUNT_DISABLED).createLogin();
- }
-
- protected Response temporarilyDisabledUser(AuthenticatorContext context) {
- return loginForm(context).setError(Messages.ACCOUNT_TEMPORARILY_DISABLED).createLogin();
- }
-
- public void validateUser(AuthenticatorContext context) {
- MultivaluedMap<String, String> inputData = context.getHttpRequest().getFormParameters();
+ public void validateUser(AuthenticatorContext context, MultivaluedMap<String, String> inputData) {
String username = inputData.getFirst(AuthenticationManager.FORM_USERNAME);
if (username == null) {
+ context.getEvent().error(Errors.USER_NOT_FOUND);
Response challengeResponse = invalidUser(context);
context.failureChallenge(AuthenticationProcessor.Error.INVALID_USER, challengeResponse);
return;
}
+ context.getEvent().detail(Details.USERNAME, username);
+ context.getClientSession().setNote("FORM_USERNAME", username);
UserModel user = KeycloakModelUtils.findUserByNameOrEmail(context.getSession(), context.getRealm(), username);
if (invalidUser(context, user)) return;
context.setUser(user);
context.success();
}
- public boolean invalidUser(AuthenticatorContext context, UserModel user) {
- if (user == null) {
- Response challengeResponse = invalidUser(context);
- context.failureChallenge(AuthenticationProcessor.Error.INVALID_USER, challengeResponse);
- return true;
- }
- if (!user.isEnabled()) {
- Response challengeResponse = disabledUser(context);
- context.failureChallenge(AuthenticationProcessor.Error.USER_DISABLED, challengeResponse);
- return true;
- }
- if (context.getRealm().isBruteForceProtected()) {
- if (context.getProtector().isTemporarilyDisabled(context.getSession(), context.getRealm(), user.getUsername())) {
- Response challengeResponse = temporarilyDisabledUser(context);
- context.failureChallenge(AuthenticationProcessor.Error.USER_TEMPORARILY_DISABLED, challengeResponse);
- 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) {
+ public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) {
return true;
}
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/OTPFormAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/OTPFormAuthenticator.java
index d93836f..b5bf2d0 100755
--- a/services/src/main/java/org/keycloak/authentication/authenticators/OTPFormAuthenticator.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/OTPFormAuthenticator.java
@@ -1,16 +1,16 @@
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.events.Errors;
import org.keycloak.login.LoginFormsProvider;
import org.keycloak.models.AuthenticatorModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
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;
@@ -24,7 +24,7 @@ import java.util.List;
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
-public class OTPFormAuthenticator implements Authenticator {
+public class OTPFormAuthenticator extends AbstractFormAuthenticator implements Authenticator {
protected AuthenticatorModel model;
public OTPFormAuthenticator(AuthenticatorModel model) {
@@ -33,8 +33,7 @@ public class OTPFormAuthenticator implements Authenticator {
@Override
public void authenticate(AuthenticatorContext context) {
- URI expected = LoginActionsService.authenticationFormProcessor(context.getUriInfo()).build(context.getRealm().getName());
- if (!expected.getPath().equals(context.getUriInfo().getPath())) {
+ if (!isActionUrl(context)) {
Response challengeResponse = challenge(context);
context.challenge(challengeResponse);
return;
@@ -48,12 +47,13 @@ public class OTPFormAuthenticator implements Authenticator {
String password = inputData.getFirst(CredentialRepresentation.TOTP);
if (password == null) {
Response challengeResponse = challenge(context);
- context.failureChallenge(AuthenticationProcessor.Error.INVALID_CREDENTIALS, challengeResponse);
+ context.challenge(challengeResponse);
return;
}
credentials.add(UserCredentialModel.totp(password));
boolean valid = context.getSession().users().validCredentials(context.getRealm(), context.getUser(), credentials);
if (!valid) {
+ context.getEvent().error(Errors.INVALID_USER_CREDENTIALS);
Response challengeResponse = challenge(context);
context.failureChallenge(AuthenticationProcessor.Error.INVALID_CREDENTIALS, challengeResponse);
return;
@@ -67,23 +67,20 @@ public class OTPFormAuthenticator implements Authenticator {
return true;
}
- protected Response challenge(AuthenticatorContext context, MultivaluedMap<String, String> formData) {
+ protected Response challenge(AuthenticatorContext context) {
+ ClientSessionCode clientSessionCode = new ClientSessionCode(context.getRealm(), context.getClientSession());
+ URI action = AbstractFormAuthenticator.getActionUrl(context, clientSessionCode);
LoginFormsProvider forms = context.getSession().getProvider(LoginFormsProvider.class)
- .setClientSessionCode(new ClientSessionCode(context.getRealm(), context.getClientSession()).getCode());
+ .setActionUri(action)
+ .setClientSessionCode(clientSessionCode.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);
+ public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) {
+ return session.users().configuredForCredentialType(UserCredentialModel.TOTP, realm, user) && user.isTotp();
}
@Override
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/SpnegoAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/SpnegoAuthenticator.java
new file mode 100755
index 0000000..1045b55
--- /dev/null
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/SpnegoAuthenticator.java
@@ -0,0 +1,123 @@
+package org.keycloak.authentication.authenticators;
+
+import org.jboss.logging.Logger;
+import org.jboss.resteasy.spi.HttpRequest;
+import org.keycloak.authentication.AuthenticationProcessor;
+import org.keycloak.authentication.Authenticator;
+import org.keycloak.authentication.AuthenticatorContext;
+import org.keycloak.constants.KerberosConstants;
+import org.keycloak.events.Errors;
+import org.keycloak.login.LoginFormsProvider;
+import org.keycloak.models.CredentialValidationOutput;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserCredentialModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.UserSessionModel;
+
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.Response;
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class SpnegoAuthenticator extends AbstractFormAuthenticator implements Authenticator{
+ protected static Logger logger = Logger.getLogger(SpnegoAuthenticator.class);
+
+ @Override
+ public boolean requiresUser() {
+ return false;
+ }
+
+ protected boolean isAlreadyChallenged(AuthenticatorContext context) {
+ UserSessionModel.AuthenticatorStatus status = context.getClientSession().getAuthenticators().get(context.getExecution().getId());
+ if (status == null) return false;
+ return status == UserSessionModel.AuthenticatorStatus.CHALLENGED;
+ }
+
+ @Override
+ public void authenticate(AuthenticatorContext context) {
+ HttpRequest request = context.getHttpRequest();
+ String authHeader = request.getHttpHeaders().getRequestHeaders().getFirst(HttpHeaders.AUTHORIZATION);
+
+ // Case when we don't yet have any Negotiate header
+ if (authHeader == null) {
+ if (isAlreadyChallenged(context)) {
+ context.attempted();
+ return;
+ }
+ Response challenge = challengeNegotiation(context, null);
+ context.challenge(challenge);
+ return;
+ }
+
+ String[] tokens = authHeader.split(" ");
+ if (tokens.length == 0) { // assume not supported
+ logger.debug("Invalid length of tokens: " + tokens.length);
+ context.attempted();
+ return;
+ }
+ if (!KerberosConstants.NEGOTIATE.equalsIgnoreCase(tokens[0])) {
+ logger.debug("Unknown scheme " + tokens[0]);
+ context.attempted();
+ return;
+ }
+ if (tokens.length != 2) {
+ context.failure(AuthenticationProcessor.Error.INVALID_CREDENTIALS);
+ return;
+ }
+
+ String spnegoToken = tokens[1];
+ UserCredentialModel spnegoCredential = UserCredentialModel.kerberos(spnegoToken);
+
+ CredentialValidationOutput output = context.getSession().users().validCredentials(context.getRealm(), spnegoCredential);
+
+ if (output.getAuthStatus() == CredentialValidationOutput.Status.AUTHENTICATED) {
+ context.setUser(output.getAuthenticatedUser());
+ if (output.getState() != null && !output.getState().isEmpty()) {
+ for (Map.Entry<String, String> entry : output.getState().entrySet()) {
+ context.getClientSession().setUserSessionNote(entry.getKey(), entry.getValue());
+ }
+ }
+ context.success();
+ } else if (output.getAuthStatus() == CredentialValidationOutput.Status.CONTINUE) {
+ String spnegoResponseToken = (String) output.getState().get(KerberosConstants.RESPONSE_TOKEN);
+ Response challenge = challengeNegotiation(context, spnegoResponseToken);
+ context.challenge(challenge);
+ } else {
+ context.getEvent().error(Errors.INVALID_USER_CREDENTIALS);
+ context.failure(AuthenticationProcessor.Error.INVALID_CREDENTIALS);
+ }
+ }
+
+ private Response challengeNegotiation(AuthenticatorContext context, final String negotiateToken) {
+ String negotiateHeader = negotiateToken == null ? KerberosConstants.NEGOTIATE : KerberosConstants.NEGOTIATE + " " + negotiateToken;
+
+ if (logger.isTraceEnabled()) {
+ logger.trace("Sending back " + HttpHeaders.WWW_AUTHENTICATE + ": " + negotiateHeader);
+ }
+ LoginFormsProvider loginForm = loginForm(context);
+
+ loginForm.setStatus(Response.Status.UNAUTHORIZED);
+ loginForm.setResponseHeader(HttpHeaders.WWW_AUTHENTICATE, negotiateHeader);
+ return loginForm.createLogin();
+ }
+
+
+ @Override
+ public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) {
+ return true;
+ }
+
+ @Override
+ public String getRequiredAction() {
+ return null;
+ }
+
+ @Override
+ public void close() {
+
+ }
+}
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/SpnegoAuthenticatorFactory.java b/services/src/main/java/org/keycloak/authentication/authenticators/SpnegoAuthenticatorFactory.java
new file mode 100755
index 0000000..a541305
--- /dev/null
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/SpnegoAuthenticatorFactory.java
@@ -0,0 +1,70 @@
+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;
+import org.keycloak.provider.ProviderConfigProperty;
+
+import java.util.List;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class SpnegoAuthenticatorFactory implements AuthenticatorFactory {
+
+ public static final String PROVIDER_ID = "auth-spnego";
+
+ @Override
+ public Authenticator create(AuthenticatorModel model) {
+ return new SpnegoAuthenticator();
+ }
+
+ @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 PROVIDER_ID;
+ }
+
+ @Override
+ public String getDisplayCategory() {
+ return "Complete Authenticator";
+ }
+
+ @Override
+ public String getDisplayType() {
+ return "SPNEGO";
+ }
+
+ @Override
+ public String getHelpText() {
+ return "Initiates the SPNEGO protocol. Most often used with Kerberos.";
+ }
+
+ @Override
+ public List<ProviderConfigProperty> getConfigProperties() {
+ return null;
+ }
+}
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java
index fdb029e..85e4067 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java
@@ -6,7 +6,6 @@ import org.jboss.resteasy.spi.HttpRequest;
import org.keycloak.ClientConnection;
import org.keycloak.OAuth2Constants;
import org.keycloak.authentication.AuthenticationProcessor;
-import org.keycloak.authentication.authenticators.AuthenticationFlow;
import org.keycloak.constants.AdapterConstants;
import org.keycloak.events.Details;
import org.keycloak.events.Errors;
@@ -44,6 +43,7 @@ import java.util.List;
public class AuthorizationEndpoint {
private static final Logger logger = Logger.getLogger(AuthorizationEndpoint.class);
+ public static final String CODE_AUTH_TYPE = "code";
private enum Action {
REGISTER, CODE
@@ -247,10 +247,18 @@ public class AuthorizationEndpoint {
return buildRedirectToIdentityProvider(idpHint, accessCode);
}
- return oldBrowserAuthentication(accessCode);
+ return newBrowserAuthentication(accessCode);
}
protected Response newBrowserAuthentication(String accessCode) {
+ List<IdentityProviderModel> identityProviders = realm.getIdentityProviders();
+ for (IdentityProviderModel identityProvider : identityProviders) {
+ if (identityProvider.isAuthenticateByDefault()) {
+ return buildRedirectToIdentityProvider(identityProvider.getAlias(), accessCode);
+ }
+ }
+ clientSession.setNote(Details.AUTH_TYPE, CODE_AUTH_TYPE);
+
String flowId = null;
for (AuthenticationFlowModel flow : realm.getAuthenticationFlows()) {
if (flow.getAlias().equals("browser")) {
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java b/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java
index 82ed245..99528c8 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java
@@ -217,6 +217,12 @@ public class TokenManager {
}
}
clientSession.setProtocolMappers(requestedProtocolMappers);
+
+ Map<String, String> transferredNotes = clientSession.getUserSessionNotes();
+ for (Map.Entry<String, String> entry : transferredNotes.entrySet()) {
+ session.setNote(entry.getKey(), entry.getValue());
+ }
+
}
public static void dettachClientSession(UserSessionProvider sessions, RealmModel realm, ClientSessionModel clientSession) {
diff --git a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
index 4329591..9221268 100755
--- a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
+++ b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
@@ -343,6 +343,7 @@ public class LoginActionsService {
} catch (AuthenticationProcessor.AuthException e) {
return handleError(e, code);
} catch (Exception e) {
+ event.error(Errors.INVALID_USER_CREDENTIALS);
logger.error("failed authentication", e);
return ErrorPage.error(session, Messages.UNEXPECTED_ERROR_HANDLING_RESPONSE);
diff --git a/services/src/main/resources/META-INF/services/org.keycloak.authentication.AuthenticatorFactory b/services/src/main/resources/META-INF/services/org.keycloak.authentication.AuthenticatorFactory
index 20dff3b..8fbf02e 100755
--- a/services/src/main/resources/META-INF/services/org.keycloak.authentication.AuthenticatorFactory
+++ b/services/src/main/resources/META-INF/services/org.keycloak.authentication.AuthenticatorFactory
@@ -2,4 +2,5 @@ org.keycloak.authentication.authenticators.CookieAuthenticatorFactory
org.keycloak.authentication.authenticators.LoginFormOTPAuthenticatorFactory
org.keycloak.authentication.authenticators.LoginFormPasswordAuthenticatorFactory
org.keycloak.authentication.authenticators.LoginFormUsernameAuthenticatorFactory
-org.keycloak.authentication.authenticators.OTPFormAuthenticatorFactory
\ No newline at end of file
+org.keycloak.authentication.authenticators.OTPFormAuthenticatorFactory
+org.keycloak.authentication.authenticators.SpnegoAuthenticatorFactory
\ No newline at end of file
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java
index b8cf2a8..5bfabae 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java
@@ -241,7 +241,7 @@ public class AccountTest {
Assert.assertEquals("Invalid username or password.", loginPage.getError());
- events.expectLogin().session((String) null).error("invalid_user_credentials").assertEvent();
+ events.expectLogin().session((String) null).user((String)null).error("invalid_user_credentials").assertEvent();
loginPage.open();
loginPage.login("test-user@localhost", "new-password");
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionTotpSetupTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionTotpSetupTest.java
index 5b12823..b759b3e 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionTotpSetupTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionTotpSetupTest.java
@@ -45,6 +45,7 @@ import org.keycloak.testsuite.rule.KeycloakRule;
import org.keycloak.testsuite.rule.KeycloakRule.KeycloakSetup;
import org.keycloak.testsuite.rule.WebResource;
import org.keycloak.testsuite.rule.WebRule;
+import org.keycloak.testsuite.utils.CredentialHelper;
import org.openqa.selenium.WebDriver;
/**
@@ -57,6 +58,7 @@ public class RequiredActionTotpSetupTest {
@Override
public void config(RealmManager manager, RealmModel defaultRealm, RealmModel appRealm) {
+ CredentialHelper.setRequiredCredential(CredentialRepresentation.TOTP, appRealm);
appRealm.addRequiredCredential(CredentialRepresentation.TOTP);
appRealm.setResetPasswordAllowed(true);
}
@@ -137,6 +139,7 @@ public class RequiredActionTotpSetupTest {
loginPage.open();
loginPage.login("test-user@localhost", "password");
+ String src = driver.getPageSource();
loginTotpPage.login(totp.generate(totpSecret));
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
@@ -181,7 +184,7 @@ public class RequiredActionTotpSetupTest {
// Login with one-time password
loginTotpPage.login(totp.generate(totpCode));
- loginEvent = events.expectLogin().user(userId).detail(Details.USERNAME, "setupTotp2").assertEvent();
+ loginEvent = events.expectLogin().user(userId).detail(Details.USERNAME, "setuptotp2").assertEvent();
// Open account page
accountTotpPage.open();
@@ -204,11 +207,11 @@ public class RequiredActionTotpSetupTest {
totpPage.assertCurrent();
totpPage.configure(totp.generate(totpPage.getTotpSecret()));
- String sessionId = events.expectRequiredAction(EventType.UPDATE_TOTP).user(userId).detail(Details.USERNAME, "setupTotp2").assertEvent().getSessionId();
+ String sessionId = events.expectRequiredAction(EventType.UPDATE_TOTP).user(userId).detail(Details.USERNAME, "setuptotp2").assertEvent().getSessionId();
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
- events.expectLogin().user(userId).session(sessionId).detail(Details.USERNAME, "setupTotp2").assertEvent();
+ events.expectLogin().user(userId).session(sessionId).detail(Details.USERNAME, "setuptotp2").assertEvent();
}
}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/AssertEvents.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/AssertEvents.java
index ce80527..ea8fab2 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/AssertEvents.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/AssertEvents.java
@@ -21,6 +21,9 @@ import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.protocol.oidc.OIDCLoginProtocol;
+import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
+import org.keycloak.protocol.oidc.endpoints.AuthorizationEndpoint;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.testsuite.rule.KeycloakRule;
@@ -119,9 +122,9 @@ public class AssertEvents implements TestRule, EventListenerProviderFactory {
public ExpectedEvent expectLogin() {
return expect(EventType.LOGIN)
.detail(Details.CODE_ID, isCodeId())
- .detail(Details.USERNAME, DEFAULT_USERNAME)
- .detail(Details.RESPONSE_TYPE, "code")
- .detail(Details.AUTH_METHOD, "form")
+ //.detail(Details.USERNAME, DEFAULT_USERNAME)
+ //.detail(Details.AUTH_METHOD, OIDCLoginProtocol.LOGIN_PROTOCOL)
+ //.detail(Details.AUTH_TYPE, AuthorizationEndpoint.CODE_AUTH_TYPE)
.detail(Details.REDIRECT_URI, DEFAULT_REDIRECT_URI)
.session(isUUID());
}
@@ -341,12 +344,13 @@ public class AssertEvents implements TestRule, EventListenerProviderFactory {
Assert.assertThat("Unexpected value for " + d.getKey(), actualValue, d.getValue());
}
-
+ /*
for (String k : actual.getDetails().keySet()) {
if (!details.containsKey(k)) {
Assert.fail(k + " was not expected");
}
}
+ */
}
return actual;
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/AbstractKerberosTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/AbstractKerberosTest.java
index 5e1930e..c5b25fd 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/AbstractKerberosTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/AbstractKerberosTest.java
@@ -108,7 +108,7 @@ public abstract class AbstractKerberosTest {
.client("kerberos-app")
.user(keycloakRule.getUser("test", "hnelson").getId())
.detail(Details.REDIRECT_URI, KERBEROS_APP_URL)
- .detail(Details.AUTH_METHOD, "spnego")
+ //.detail(Details.AUTH_METHOD, "spnego")
.detail(Details.USERNAME, "hnelson")
.assertEvent();
@@ -164,7 +164,7 @@ public abstract class AbstractKerberosTest {
.client("kerberos-app")
.user(keycloakRule.getUser("test", "jduke").getId())
.detail(Details.REDIRECT_URI, KERBEROS_APP_URL)
- .detail(Details.AUTH_METHOD, "spnego")
+ //.detail(Details.AUTH_METHOD, "spnego")
.detail(Details.USERNAME, "jduke")
.assertEvent();
spnegoResponse.close();
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/KerberosStandaloneTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/KerberosStandaloneTest.java
old mode 100644
new mode 100755
index 699d85b..2841e25
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/KerberosStandaloneTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/KerberosStandaloneTest.java
@@ -18,11 +18,14 @@ import org.keycloak.constants.KerberosConstants;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserFederationProviderModel;
+import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.testsuite.AssertEvents;
import org.keycloak.testsuite.rule.KerberosRule;
import org.keycloak.testsuite.rule.KeycloakRule;
import org.keycloak.testsuite.rule.WebRule;
+import org.keycloak.testsuite.utils.CredentialHelper;
+import org.picketlink.idm.credential.util.CredentialUtils;
/**
* Test of KerberosFederationProvider (Kerberos not backed by LDAP)
@@ -41,6 +44,8 @@ public class KerberosStandaloneTest extends AbstractKerberosTest {
@Override
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+
+ CredentialHelper.setRequiredCredential(CredentialRepresentation.KERBEROS, appRealm);
URL url = getClass().getResource("/kerberos-test/kerberos-app-keycloak.json");
keycloakRule.deployApplication("kerberos-portal", "/kerberos-portal", KerberosCredDelegServlet.class, url.getPath(), "user");
@@ -131,4 +136,6 @@ public class KerberosStandaloneTest extends AbstractKerberosTest {
keycloakRule.stopSession(session, true);
}
}
+
+
}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/utils/CredentialHelper.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/utils/CredentialHelper.java
new file mode 100755
index 0000000..3018fac
--- /dev/null
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/utils/CredentialHelper.java
@@ -0,0 +1,80 @@
+package org.keycloak.testsuite.utils;
+
+import org.keycloak.authentication.authenticators.LoginFormPasswordAuthenticatorFactory;
+import org.keycloak.authentication.authenticators.OTPFormAuthenticator;
+import org.keycloak.authentication.authenticators.OTPFormAuthenticatorFactory;
+import org.keycloak.authentication.authenticators.SpnegoAuthenticator;
+import org.keycloak.authentication.authenticators.SpnegoAuthenticatorFactory;
+import org.keycloak.models.AuthenticationExecutionModel;
+import org.keycloak.models.AuthenticationFlowModel;
+import org.keycloak.models.AuthenticatorModel;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.utils.DefaultAuthenticationFlows;
+import org.keycloak.representations.idm.CredentialRepresentation;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class CredentialHelper {
+
+ public static void setRequiredCredential(String type, RealmModel realm) {
+ if (type.equals(CredentialRepresentation.TOTP)) {
+ String providerId = OTPFormAuthenticatorFactory.PROVIDER_ID;
+ String flowAlias = DefaultAuthenticationFlows.FORMS_FLOW;
+ requireAuthentication(realm, providerId, flowAlias);
+ } else if (type.equals(CredentialRepresentation.KERBEROS)) {
+ String providerId = SpnegoAuthenticatorFactory.PROVIDER_ID;
+ String flowAlias = DefaultAuthenticationFlows.BROWSER_FLOW;
+ alternativeAuthentication(realm, providerId, flowAlias);
+ } else if (type.equals(CredentialRepresentation.PASSWORD)) {
+ String providerId = LoginFormPasswordAuthenticatorFactory.PROVIDER_ID;
+ String flowAlias = DefaultAuthenticationFlows.FORMS_FLOW;
+ requireAuthentication(realm, providerId, flowAlias);
+ }
+ }
+
+ public static void requireAuthentication(RealmModel realm, String authenticatorProviderId, String flowAlias) {
+ AuthenticationExecutionModel.Requirement requirement = AuthenticationExecutionModel.Requirement.REQUIRED;
+ authenticationRequirement(realm, authenticatorProviderId, flowAlias, requirement);
+ }
+
+ public static void alternativeAuthentication(RealmModel realm, String authenticatorProviderId, String flowAlias) {
+ AuthenticationExecutionModel.Requirement requirement = AuthenticationExecutionModel.Requirement.ALTERNATIVE;
+ authenticationRequirement(realm, authenticatorProviderId, flowAlias, requirement);
+ }
+
+ public static void authenticationRequirement(RealmModel realm, String authenticatorProviderId, String flowAlias, AuthenticationExecutionModel.Requirement requirement) {
+ AuthenticatorModel authenticator = findAuthenticatorByProviderId(realm, authenticatorProviderId);
+ AuthenticationFlowModel flow = findAuthenticatorFlowByAlias(realm, flowAlias);
+ AuthenticationExecutionModel execution = findExecutionByAuthenticator(realm, flow.getId(), authenticator.getId());
+ execution.setRequirement(requirement);
+ realm.updateAuthenticatorExecution(execution);
+ }
+
+ public static AuthenticatorModel findAuthenticatorByProviderId(RealmModel realm, String providerId) {
+ for (AuthenticatorModel model : realm.getAuthenticators()) {
+ if (model.getProviderId().equals(providerId)) {
+ return model;
+ }
+ }
+ return null;
+ }
+ public static AuthenticationFlowModel findAuthenticatorFlowByAlias(RealmModel realm, String alias) {
+ for (AuthenticationFlowModel model : realm.getAuthenticationFlows()) {
+ if (model.getAlias().equals(alias)) {
+ return model;
+ }
+ }
+ return null;
+ }
+ public static AuthenticationExecutionModel findExecutionByAuthenticator(RealmModel realm, String flowId, String authId) {
+ for (AuthenticationExecutionModel model : realm.getAuthenticationExecutions(flowId)) {
+ if (model.getAuthenticator().equals(authId)) {
+ return model;
+ }
+ }
+ return null;
+
+ }
+}