diff --git a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.5.0.xml b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.5.0.xml
index 4866834caa..c80f25c6a8 100755
--- a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.5.0.xml
+++ b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.5.0.xml
@@ -43,6 +43,15 @@
+
+
+
+
+
+
+
+
+
diff --git a/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
index d93a0d693d..43aeb613a3 100755
--- a/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
@@ -87,6 +87,9 @@ public class RealmRepresentation {
protected List authenticationFlows;
protected List authenticatorConfig;
protected List requiredActions;
+ protected String browserFlow;
+ protected String registrationFlow;
+ protected String directGrantFlow;
@Deprecated
protected Boolean social;
@@ -708,4 +711,28 @@ public class RealmRepresentation {
public void setOtpPolicyPeriod(Integer otpPolicyPeriod) {
this.otpPolicyPeriod = otpPolicyPeriod;
}
+
+ public String getBrowserFlow() {
+ return browserFlow;
+ }
+
+ public void setBrowserFlow(String browserFlow) {
+ this.browserFlow = browserFlow;
+ }
+
+ public String getRegistrationFlow() {
+ return registrationFlow;
+ }
+
+ public void setRegistrationFlow(String registrationFlow) {
+ this.registrationFlow = registrationFlow;
+ }
+
+ public String getDirectGrantFlow() {
+ return directGrantFlow;
+ }
+
+ public void setDirectGrantFlow(String directGrantFlow) {
+ this.directGrantFlow = directGrantFlow;
+ }
}
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/app.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/app.js
index 878f13da57..19a3562ccb 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/app.js
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/app.js
@@ -1087,6 +1087,21 @@ module.config([ '$routeProvider', function($routeProvider) {
},
controller : 'AuthenticationFlowsCtrl'
})
+ .when('/realms/:realm/authentication/flow-bindings', {
+ templateUrl : resourceUrl + '/partials/authentication-flow-bindings.html',
+ resolve : {
+ realm : function(RealmLoader) {
+ return RealmLoader();
+ },
+ flows : function(AuthenticationFlowsLoader) {
+ return AuthenticationFlowsLoader();
+ },
+ serverInfo : function(ServerInfo) {
+ return ServerInfo.delay;
+ }
+ },
+ controller : 'RealmFlowBindingCtrl'
+ })
.when('/realms/:realm/authentication/flows/:flow', {
templateUrl : resourceUrl + '/partials/authentication-flows.html',
resolve : {
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js
index 0ed6fa4667..9aeb0e8a73 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js
@@ -376,6 +376,7 @@ module.controller('RealmOtpPolicyCtrl', function($scope, Current, Realm, realm,
genericRealmUpdate($scope, Current, Realm, realm, serverInfo, $http, $location, Dialog, Notifications, "/realms/" + realm.realm + "/authentication/otp-policy");
});
+
module.controller('RealmThemeCtrl', function($scope, Current, Realm, realm, serverInfo, $http, $location, Dialog, Notifications) {
genericRealmUpdate($scope, Current, Realm, realm, serverInfo, $http, $location, Dialog, Notifications, "/realms/" + realm.realm + "/theme-settings");
@@ -1620,6 +1621,13 @@ module.controller('IdentityProviderMapperCreateCtrl', function($scope, realm, id
});
+module.controller('RealmFlowBindingCtrl', function($scope, flows, Current, Realm, realm, serverInfo, $http, $location, Dialog, Notifications) {
+ $scope.flows = flows;
+
+ genericRealmUpdate($scope, Current, Realm, realm, serverInfo, $http, $location, Dialog, Notifications, "/realms/" + realm.realm + "/authentication/flow-bindings");
+});
+
+
module.controller('CreateFlowCtrl', function($scope, realm,
AuthenticationFlows,
Notifications, $location) {
@@ -1770,8 +1778,9 @@ module.controller('AuthenticationFlowsCtrl', function($scope, $route, realm, flo
};
$scope.removeFlow = function() {
- AuthenticationFlows.remove({realm: realm, flow: flow.id}, function() {
- $route.reload();
+ console.log('Remove flow:' + $scope.flow.alias);
+ AuthenticationFlows.remove({realm: realm.realm, flow: $scope.flow.id}, function() {
+ $location.url("/realms/" + realm.realm + '/authentication/flows');
Notifications.success("Flow removed");
})
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/authentication-flow-bindings.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/authentication-flow-bindings.html
new file mode 100755
index 0000000000..050c5f17fa
--- /dev/null
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/authentication-flow-bindings.html
@@ -0,0 +1,49 @@
+
+
Authentication
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-authentication.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-authentication.html
index 214a9ad084..0fcd65a440 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-authentication.html
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-authentication.html
@@ -1,5 +1,6 @@
- Flows
+ - Bindings
- Required Actions
- Password Policy
- OTP Policy
diff --git a/model/api/src/main/java/org/keycloak/migration/migrators/MigrateTo1_5_0.java b/model/api/src/main/java/org/keycloak/migration/migrators/MigrateTo1_5_0.java
index 2b35150592..acec3b0660 100755
--- a/model/api/src/main/java/org/keycloak/migration/migrators/MigrateTo1_5_0.java
+++ b/model/api/src/main/java/org/keycloak/migration/migrators/MigrateTo1_5_0.java
@@ -24,7 +24,10 @@ public class MigrateTo1_5_0 {
public void migrate(KeycloakSession session) {
List realms = session.realms().getRealms();
for (RealmModel realm : realms) {
- realm.setOTPPolicy(OTPPolicy.DEFAULT_POLICY);
+ realm.setOTPPolicy(OTPPolicy.DEFAULT_POLICY);
+ realm.setBrowserFlow(realm.getFlowByAlias(DefaultAuthenticationFlows.BROWSER_FLOW));
+ realm.setRegistrationFlow(realm.getFlowByAlias(DefaultAuthenticationFlows.REGISTRATION_FLOW));
+ realm.setDirectGrantFlow(realm.getFlowByAlias(DefaultAuthenticationFlows.DIRECT_GRANT_FLOW));
}
}
diff --git a/model/api/src/main/java/org/keycloak/models/RealmModel.java b/model/api/src/main/java/org/keycloak/models/RealmModel.java
index a9b81a64cf..9f393778b9 100755
--- a/model/api/src/main/java/org/keycloak/models/RealmModel.java
+++ b/model/api/src/main/java/org/keycloak/models/RealmModel.java
@@ -182,6 +182,15 @@ public interface RealmModel extends RoleContainerModel {
void setSmtpConfig(Map smtpConfig);
+ AuthenticationFlowModel getBrowserFlow();
+ void setBrowserFlow(AuthenticationFlowModel flow);
+
+ AuthenticationFlowModel getRegistrationFlow();
+ void setRegistrationFlow(AuthenticationFlowModel flow);
+
+ AuthenticationFlowModel getDirectGrantFlow();
+ void setDirectGrantFlow(AuthenticationFlowModel flow);
+
List getAuthenticationFlows();
AuthenticationFlowModel getFlowByAlias(String alias);
AuthenticationFlowModel addAuthenticationFlow(AuthenticationFlowModel model);
diff --git a/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java b/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java
index 64228e4fa6..1d4859d4f5 100755
--- a/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java
+++ b/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java
@@ -86,6 +86,9 @@ public class RealmEntity extends AbstractIdentifiableEntity {
private List authenticationFlows = new ArrayList<>();
private List authenticatorConfigs = new ArrayList<>();
private List requiredActionProviders = new ArrayList<>();
+ private String browserFlow;
+ private String registrationFlow;
+ private String directGrantFlow;
public String getName() {
@@ -566,6 +569,30 @@ public class RealmEntity extends AbstractIdentifiableEntity {
public void setOtpPolicyPeriod(int otpPolicyPeriod) {
this.otpPolicyPeriod = otpPolicyPeriod;
}
+
+ public String getBrowserFlow() {
+ return browserFlow;
+ }
+
+ public void setBrowserFlow(String browserFlow) {
+ this.browserFlow = browserFlow;
+ }
+
+ public String getRegistrationFlow() {
+ return registrationFlow;
+ }
+
+ public void setRegistrationFlow(String registrationFlow) {
+ this.registrationFlow = registrationFlow;
+ }
+
+ public String getDirectGrantFlow() {
+ return directGrantFlow;
+ }
+
+ public void setDirectGrantFlow(String directGrantFlow) {
+ this.directGrantFlow = directGrantFlow;
+ }
}
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 7995887b81..2c0b8e6048 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
@@ -39,6 +39,7 @@ public class DefaultAuthenticationFlows {
registrationFlow.setTopLevel(true);
registrationFlow.setBuiltIn(true);
registrationFlow = realm.addAuthenticationFlow(registrationFlow);
+ realm.setRegistrationFlow(registrationFlow);
AuthenticationFlowModel registrationFormFlow = new AuthenticationFlowModel();
registrationFormFlow.setAlias(REGISTRATION_FORM_FLOW);
@@ -125,6 +126,7 @@ public class DefaultAuthenticationFlows {
grant.setTopLevel(true);
grant.setBuiltIn(true);
grant = realm.addAuthenticationFlow(grant);
+ realm.setDirectGrantFlow(grant);
// username
AuthenticationExecutionModel execution = new AuthenticationExecutionModel();
@@ -171,6 +173,8 @@ public class DefaultAuthenticationFlows {
browser.setTopLevel(true);
browser.setBuiltIn(true);
browser = realm.addAuthenticationFlow(browser);
+ realm.setBrowserFlow(browser);
+
AuthenticationExecutionModel execution = new AuthenticationExecutionModel();
execution.setParentFlow(browser.getId());
execution.setRequirement(AuthenticationExecutionModel.Requirement.ALTERNATIVE);
diff --git a/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java b/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
index 39a936d7fe..f0583ba917 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
@@ -160,6 +160,9 @@ public class ModelToRepresentation {
rep.setOtpPolicyInitialCounter(otpPolicy.getInitialCounter());
rep.setOtpPolicyType(otpPolicy.getType());
rep.setOtpPolicyLookAheadWindow(otpPolicy.getLookAheadWindow());
+ if (realm.getBrowserFlow() != null) rep.setBrowserFlow(realm.getBrowserFlow().getAlias());
+ if (realm.getRegistrationFlow() != null) rep.setRegistrationFlow(realm.getRegistrationFlow().getAlias());
+ if (realm.getDirectGrantFlow() != null) rep.setDirectGrantFlow(realm.getDirectGrantFlow().getAlias());
List defaultRoles = realm.getDefaultRoles();
if (!defaultRoles.isEmpty()) {
diff --git a/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java b/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
index f90e7b92ca..940307ccb7 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
@@ -343,7 +343,22 @@ public class RepresentationToModel {
newRealm.addAuthenticatorExecution(execution);
}
}
- }
+ }
+ if (rep.getBrowserFlow() == null) {
+ newRealm.setBrowserFlow(newRealm.getFlowByAlias(DefaultAuthenticationFlows.BROWSER_FLOW));
+ } else {
+ newRealm.setBrowserFlow(newRealm.getFlowByAlias(rep.getBrowserFlow()));
+ }
+ if (rep.getRegistrationFlow() == null) {
+ newRealm.setRegistrationFlow(newRealm.getFlowByAlias(DefaultAuthenticationFlows.REGISTRATION_FLOW));
+ } else {
+ newRealm.setRegistrationFlow(newRealm.getFlowByAlias(rep.getRegistrationFlow()));
+ }
+ if (rep.getDirectGrantFlow() == null) {
+ newRealm.setDirectGrantFlow(newRealm.getFlowByAlias(DefaultAuthenticationFlows.DIRECT_GRANT_FLOW));
+ } else {
+ newRealm.setDirectGrantFlow(newRealm.getFlowByAlias(rep.getDirectGrantFlow()));
+ }
}
@@ -542,6 +557,15 @@ public class RepresentationToModel {
if(rep.getDefaultLocale() != null){
realm.setDefaultLocale(rep.getDefaultLocale());
}
+ if (rep.getBrowserFlow() != null) {
+ realm.setBrowserFlow(realm.getFlowByAlias(rep.getBrowserFlow()));
+ }
+ if (rep.getRegistrationFlow() != null) {
+ realm.setRegistrationFlow(realm.getFlowByAlias(rep.getRegistrationFlow()));
+ }
+ if (rep.getDirectGrantFlow() != null) {
+ realm.setDirectGrantFlow(realm.getFlowByAlias(rep.getDirectGrantFlow()));
+ }
}
// Basic realm stuff
diff --git a/model/file/src/main/java/org/keycloak/models/file/adapter/RealmAdapter.java b/model/file/src/main/java/org/keycloak/models/file/adapter/RealmAdapter.java
index d4b39167db..535328f7d2 100755
--- a/model/file/src/main/java/org/keycloak/models/file/adapter/RealmAdapter.java
+++ b/model/file/src/main/java/org/keycloak/models/file/adapter/RealmAdapter.java
@@ -1230,6 +1230,46 @@ public class RealmAdapter implements RealmModel {
return mapping;
}
+ @Override
+ public AuthenticationFlowModel getBrowserFlow() {
+ String flowId = realm.getBrowserFlow();
+ if (flowId == null) return null;
+ return getAuthenticationFlowById(flowId);
+ }
+
+ @Override
+ public void setBrowserFlow(AuthenticationFlowModel flow) {
+ realm.setBrowserFlow(flow.getId());
+
+ }
+
+ @Override
+ public AuthenticationFlowModel getRegistrationFlow() {
+ String flowId = realm.getRegistrationFlow();
+ if (flowId == null) return null;
+ return getAuthenticationFlowById(flowId);
+ }
+
+ @Override
+ public void setRegistrationFlow(AuthenticationFlowModel flow) {
+ realm.setRegistrationFlow(flow.getId());
+
+ }
+
+ @Override
+ public AuthenticationFlowModel getDirectGrantFlow() {
+ String flowId = realm.getDirectGrantFlow();
+ if (flowId == null) return null;
+ return getAuthenticationFlowById(flowId);
+ }
+
+ @Override
+ public void setDirectGrantFlow(AuthenticationFlowModel flow) {
+ realm.setDirectGrantFlow(flow.getId());
+
+ }
+
+
@Override
public List getAuthenticationFlows() {
List flows = realm.getAuthenticationFlows();
diff --git a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmAdapter.java b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmAdapter.java
index cb38a53b8b..7526f50122 100755
--- a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmAdapter.java
+++ b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmAdapter.java
@@ -1031,6 +1031,45 @@ public class RealmAdapter implements RealmModel {
return null;
}
+ @Override
+ public AuthenticationFlowModel getBrowserFlow() {
+ if (updated != null) return updated.getBrowserFlow();
+ return cached.getBrowserFlow();
+ }
+
+ @Override
+ public void setBrowserFlow(AuthenticationFlowModel flow) {
+ getDelegateForUpdate();
+ updated.setBrowserFlow(flow);
+
+ }
+
+ @Override
+ public AuthenticationFlowModel getRegistrationFlow() {
+ if (updated != null) return updated.getRegistrationFlow();
+ return cached.getRegistrationFlow();
+ }
+
+ @Override
+ public void setRegistrationFlow(AuthenticationFlowModel flow) {
+ getDelegateForUpdate();
+ updated.setRegistrationFlow(flow);
+
+ }
+
+ @Override
+ public AuthenticationFlowModel getDirectGrantFlow() {
+ if (updated != null) return updated.getDirectGrantFlow();
+ return cached.getDirectGrantFlow();
+ }
+
+ @Override
+ public void setDirectGrantFlow(AuthenticationFlowModel flow) {
+ getDelegateForUpdate();
+ updated.setDirectGrantFlow(flow);
+
+ }
+
@Override
public List getAuthenticationFlows() {
if (updated != null) return updated.getAuthenticationFlows();
diff --git a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java
index e93d4159e7..a406d9db96 100755
--- a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java
+++ b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java
@@ -91,6 +91,10 @@ public class CachedRealm implements Serializable {
private MultivaluedHashMap authenticationExecutions = new MultivaluedHashMap<>();
private Map executionsById = new HashMap<>();
+ private AuthenticationFlowModel browserFlow;
+ private AuthenticationFlowModel registrationFlow;
+ private AuthenticationFlowModel directGrantFlow;
+
private boolean eventsEnabled;
private long eventsExpiration;
private Set eventsListeners = new HashSet();
@@ -214,6 +218,10 @@ public class CachedRealm implements Serializable {
requiredActionProvidersByAlias.put(action.getAlias(), action);
}
+ browserFlow = model.getBrowserFlow();
+ registrationFlow = model.getRegistrationFlow();
+ directGrantFlow = model.getDirectGrantFlow();
+
}
@@ -463,4 +471,16 @@ public class CachedRealm implements Serializable {
public OTPPolicy getOtpPolicy() {
return otpPolicy;
}
+
+ public AuthenticationFlowModel getBrowserFlow() {
+ return browserFlow;
+ }
+
+ public AuthenticationFlowModel getRegistrationFlow() {
+ return registrationFlow;
+ }
+
+ public AuthenticationFlowModel getDirectGrantFlow() {
+ return directGrantFlow;
+ }
}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
index 9706393767..15ca36f231 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
@@ -1541,6 +1541,45 @@ public class RealmAdapter implements RealmModel {
return mapper;
}
+ @Override
+ public AuthenticationFlowModel getBrowserFlow() {
+ String flowId = realm.getBrowserFlow();
+ if (flowId == null) return null;
+ return getAuthenticationFlowById(flowId);
+ }
+
+ @Override
+ public void setBrowserFlow(AuthenticationFlowModel flow) {
+ realm.setBrowserFlow(flow.getId());
+
+ }
+
+ @Override
+ public AuthenticationFlowModel getRegistrationFlow() {
+ String flowId = realm.getRegistrationFlow();
+ if (flowId == null) return null;
+ return getAuthenticationFlowById(flowId);
+ }
+
+ @Override
+ public void setRegistrationFlow(AuthenticationFlowModel flow) {
+ realm.setRegistrationFlow(flow.getId());
+
+ }
+
+ @Override
+ public AuthenticationFlowModel getDirectGrantFlow() {
+ String flowId = realm.getDirectGrantFlow();
+ if (flowId == null) return null;
+ return getAuthenticationFlowById(flowId);
+ }
+
+ @Override
+ public void setDirectGrantFlow(AuthenticationFlowModel flow) {
+ realm.setDirectGrantFlow(flow.getId());
+
+ }
+
@Override
public List getAuthenticationFlows() {
TypedQuery query = em.createNamedQuery("getAuthenticationFlowsByRealm", AuthenticationFlowEntity.class);
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java
index 67ba5a61b0..ddb6e34de7 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java
@@ -179,6 +179,17 @@ public class RealmEntity {
@OneToMany(cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm")
Collection authenticationFlows = new ArrayList<>();
+ @Column(name="BROWSER_FLOW")
+ protected String browserFlow;
+
+ @Column(name="REGISTRATION_FLOW")
+ protected String registrationFlow;
+
+
+ @Column(name="DIRECT_GRANT_FLOW")
+ protected String directGrantFlow;
+
+
@Column(name="INTERNATIONALIZATION_ENABLED")
@@ -643,5 +654,29 @@ public class RealmEntity {
public void setOtpPolicyPeriod(int otpPolicyPeriod) {
this.otpPolicyPeriod = otpPolicyPeriod;
}
+
+ public String getBrowserFlow() {
+ return browserFlow;
+ }
+
+ public void setBrowserFlow(String browserFlow) {
+ this.browserFlow = browserFlow;
+ }
+
+ public String getRegistrationFlow() {
+ return registrationFlow;
+ }
+
+ public void setRegistrationFlow(String registrationFlow) {
+ this.registrationFlow = registrationFlow;
+ }
+
+ public String getDirectGrantFlow() {
+ return directGrantFlow;
+ }
+
+ public void setDirectGrantFlow(String directGrantFlow) {
+ this.directGrantFlow = directGrantFlow;
+ }
}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
index d5d31ebf59..295ba6d8b6 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
@@ -1309,6 +1309,49 @@ public class RealmAdapter extends AbstractMongoAdapter impleme
return mapping;
}
+ @Override
+ public AuthenticationFlowModel getBrowserFlow() {
+ String flowId = realm.getBrowserFlow();
+ if (flowId == null) return null;
+ return getAuthenticationFlowById(flowId);
+ }
+
+ @Override
+ public void setBrowserFlow(AuthenticationFlowModel flow) {
+ realm.setBrowserFlow(flow.getId());
+ updateRealm();
+
+ }
+
+ @Override
+ public AuthenticationFlowModel getRegistrationFlow() {
+ String flowId = realm.getRegistrationFlow();
+ if (flowId == null) return null;
+ return getAuthenticationFlowById(flowId);
+ }
+
+ @Override
+ public void setRegistrationFlow(AuthenticationFlowModel flow) {
+ realm.setRegistrationFlow(flow.getId());
+ updateRealm();
+
+ }
+
+ @Override
+ public AuthenticationFlowModel getDirectGrantFlow() {
+ String flowId = realm.getDirectGrantFlow();
+ if (flowId == null) return null;
+ return getAuthenticationFlowById(flowId);
+ }
+
+ @Override
+ public void setDirectGrantFlow(AuthenticationFlowModel flow) {
+ realm.setDirectGrantFlow(flow.getId());
+ updateRealm();
+
+ }
+
+
@Override
public List getAuthenticationFlows() {
List flows = getMongoEntity().getAuthenticationFlows();
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 9b93942563..8c0481fe84 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
@@ -501,7 +501,7 @@ public class SamlService {
return buildRedirectToIdentityProvider(identityProvider.getAlias(), new ClientSessionCode(realm, clientSession).getCode() );
}
}
- AuthenticationFlowModel flow = realm.getFlowByAlias(DefaultAuthenticationFlows.BROWSER_FLOW);
+ AuthenticationFlowModel flow = realm.getBrowserFlow();
String flowId = flow.getId();
AuthenticationProcessor processor = new AuthenticationProcessor();
processor.setClientSession(clientSession)
diff --git a/services/src/main/java/org/keycloak/authentication/Authenticator.java b/services/src/main/java/org/keycloak/authentication/Authenticator.java
index 674f892be0..932e4adcb9 100755
--- a/services/src/main/java/org/keycloak/authentication/Authenticator.java
+++ b/services/src/main/java/org/keycloak/authentication/Authenticator.java
@@ -6,12 +6,36 @@ import org.keycloak.models.UserModel;
import org.keycloak.provider.Provider;
/**
-* @author Bill Burke
-* @version $Revision: 1 $
-*/
+ * This interface is for users that want to add custom authenticators to an authentication flow.
+ * You must implement this interface as well as an AuthenticatorFactory.
+ *
+ * @author Bill Burke
+ * @version $Revision: 1 $
+ */
public interface Authenticator extends Provider {
+
+ /**
+ * Initial call for the authenticator. If this is a form, a challenge with a Response rendering the form is usually sent
+ *
+ * @param context
+ */
void authenticate(AuthenticatorContext context);
+
+ /**
+ * Does this authenticator require that the user has already been identified? That AuthenticatorContext.getUser() is not null?
+ *
+ * @return
+ */
boolean requiresUser();
+
+ /**
+ * Is this authenticator configured for this user.
+ *
+ * @param session
+ * @param realm
+ * @param user
+ * @return
+ */
boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user);
/**
@@ -20,6 +44,11 @@ public interface Authenticator extends Provider {
*/
void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user);
+ /**
+ * Usually implements a form action.
+ *
+ * @param context
+ */
void action(AuthenticatorContext context);
diff --git a/services/src/main/java/org/keycloak/authentication/AuthenticatorFactory.java b/services/src/main/java/org/keycloak/authentication/AuthenticatorFactory.java
index 9ed937167f..7e5e691c6d 100755
--- a/services/src/main/java/org/keycloak/authentication/AuthenticatorFactory.java
+++ b/services/src/main/java/org/keycloak/authentication/AuthenticatorFactory.java
@@ -6,6 +6,12 @@ import org.keycloak.provider.ConfiguredProvider;
import org.keycloak.provider.ProviderFactory;
/**
+ * Factory for creating Authenticator instances
+ *
+ * You must specify a file
+ * META-INF/services/org.keycloak.authentication.AuthenticatorFactory in the jar that this class is contained in
+ * This file must have the fully qualified class name of all your AuthentitoryFactory classes
+ *
* @author Bill Burke
* @version $Revision: 1 $
*/
diff --git a/services/src/main/java/org/keycloak/authentication/ConfigurableAuthenticatorFactory.java b/services/src/main/java/org/keycloak/authentication/ConfigurableAuthenticatorFactory.java
index ab807ca28b..48dbcf805e 100755
--- a/services/src/main/java/org/keycloak/authentication/ConfigurableAuthenticatorFactory.java
+++ b/services/src/main/java/org/keycloak/authentication/ConfigurableAuthenticatorFactory.java
@@ -8,6 +8,11 @@ import org.keycloak.provider.ConfiguredProvider;
* @version $Revision: 1 $
*/
public interface ConfigurableAuthenticatorFactory extends ConfiguredProvider {
+ /**
+ * Friendly name for the authenticator
+ *
+ * @return
+ */
String getDisplayType();
/**
@@ -17,6 +22,11 @@ public interface ConfigurableAuthenticatorFactory extends ConfiguredProvider {
*/
String getReferenceCategory();
+ /**
+ * Is this authenticator configurable?
+ *
+ * @return
+ */
boolean isConfigurable();
/**
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 64a26d4a69..e368029104 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
@@ -263,7 +263,7 @@ public class AuthorizationEndpoint {
clientSession.setNote(Details.AUTH_TYPE, CODE_AUTH_TYPE);
- AuthenticationFlowModel flow = realm.getFlowByAlias(DefaultAuthenticationFlows.BROWSER_FLOW);
+ AuthenticationFlowModel flow = realm.getBrowserFlow();
String flowId = flow.getId();
AuthenticationProcessor processor = new AuthenticationProcessor();
processor.setClientSession(clientSession)
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java
index b6dfcd793b..9fb6137009 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java
@@ -331,7 +331,7 @@ public class TokenEndpoint {
clientSession.setAuthMethod(OIDCLoginProtocol.LOGIN_PROTOCOL);
clientSession.setAction(ClientSessionModel.Action.AUTHENTICATE.name());
clientSession.setNote(OIDCLoginProtocol.ISSUER, Urls.realmIssuer(uriInfo.getBaseUri(), realm.getName()));
- AuthenticationFlowModel flow = realm.getFlowByAlias(DefaultAuthenticationFlows.DIRECT_GRANT_FLOW);
+ AuthenticationFlowModel flow = realm.getDirectGrantFlow();
String flowId = flow.getId();
AuthenticationProcessor processor = new AuthenticationProcessor();
processor.setClientSession(clientSession)
diff --git a/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java b/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java
index 4476557769..3ac8af5e1a 100755
--- a/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java
+++ b/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java
@@ -469,7 +469,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
protected Response browserAuthentication(ClientSessionModel clientSession, String errorMessage) {
this.event.event(EventType.LOGIN);
- AuthenticationFlowModel flow = realmModel.getFlowByAlias(DefaultAuthenticationFlows.BROWSER_FLOW);
+ AuthenticationFlowModel flow = realmModel.getBrowserFlow();
String flowId = flow.getId();
AuthenticationProcessor processor = new AuthenticationProcessor();
processor.setClientSession(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 c6b77b561f..dc83039c93 100755
--- a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
+++ b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
@@ -152,7 +152,7 @@ public class LoginActionsService {
ClientSessionCode clientCode;
Response response;
- boolean verifyCode(String flow, String code, String requiredAction) {
+ boolean verifyCode(AuthenticationFlowModel flow, String code, String requiredAction) {
if (!verifyCode(flow, code)) {
return false;
} else if (!clientCode.isValidAction(requiredAction)) {
@@ -175,7 +175,7 @@ public class LoginActionsService {
}
}
- boolean verifyCode(String flow, String code, String requiredAction, String alternativeRequiredAction) {
+ boolean verifyCode(AuthenticationFlowModel flow, String code, String requiredAction, String alternativeRequiredAction) {
if (!verifyCode(flow, code)) {
return false;
} else if (!(clientCode.isValidAction(requiredAction) || clientCode.isValidAction(alternativeRequiredAction))) {
@@ -201,7 +201,7 @@ public class LoginActionsService {
}
}
- public boolean verifyCode(String flow, String code) {
+ public boolean verifyCode(AuthenticationFlowModel flow, String code) {
if (!checkSsl()) {
event.error(Errors.SSL_REQUIRED);
response = ErrorPage.error(session, Messages.HTTPS_REQUIRED);
@@ -268,7 +268,7 @@ public class LoginActionsService {
@QueryParam("execution") String execution) {
event.event(EventType.LOGIN);
Checks checks = new Checks();
- if (!checks.verifyCode(DefaultAuthenticationFlows.BROWSER_FLOW, code, ClientSessionModel.Action.AUTHENTICATE.name(), ClientSessionModel.Action.RECOVER_PASSWORD.name())) {
+ if (!checks.verifyCode(realm.getBrowserFlow(), code, ClientSessionModel.Action.AUTHENTICATE.name(), ClientSessionModel.Action.RECOVER_PASSWORD.name())) {
return checks.response;
}
event.detail(Details.CODE_ID, code);
@@ -284,12 +284,10 @@ public class LoginActionsService {
}
protected Response processAuthentication(String execution, ClientSessionModel clientSession, String errorMessage) {
- String flowAlias = DefaultAuthenticationFlows.BROWSER_FLOW;
- return processFlow(execution, clientSession, flowAlias, errorMessage);
+ return processFlow(execution, clientSession, realm.getBrowserFlow(), errorMessage);
}
- protected Response processFlow(String execution, ClientSessionModel clientSession, String flowAlias, String errorMessage) {
- AuthenticationFlowModel flow = realm.getFlowByAlias(flowAlias);
+ protected Response processFlow(String execution, ClientSessionModel clientSession, AuthenticationFlowModel flow, String errorMessage) {
AuthenticationProcessor processor = new AuthenticationProcessor();
processor.setClientSession(clientSession)
.setFlowId(flow.getId())
@@ -325,7 +323,7 @@ public class LoginActionsService {
@QueryParam("execution") String execution) {
event.event(EventType.LOGIN);
Checks checks = new Checks();
- if (!checks.verifyCode(DefaultAuthenticationFlows.BROWSER_FLOW, code, ClientSessionModel.Action.AUTHENTICATE.name())) {
+ if (!checks.verifyCode(realm.getBrowserFlow(), code, ClientSessionModel.Action.AUTHENTICATE.name())) {
return checks.response;
}
final ClientSessionCode clientCode = checks.clientCode;
@@ -335,8 +333,7 @@ public class LoginActionsService {
}
protected Response processRegistration(String execution, ClientSessionModel clientSession, String errorMessage) {
- String flowAlias = DefaultAuthenticationFlows.REGISTRATION_FLOW;
- return processFlow(execution, clientSession, flowAlias, errorMessage);
+ return processFlow(execution, clientSession, realm.getRegistrationFlow(), errorMessage);
}
@@ -357,7 +354,7 @@ public class LoginActionsService {
}
Checks checks = new Checks();
- if (!checks.verifyCode(DefaultAuthenticationFlows.REGISTRATION_FLOW, code, ClientSessionModel.Action.AUTHENTICATE.name())) {
+ if (!checks.verifyCode(realm.getRegistrationFlow(), code, ClientSessionModel.Action.AUTHENTICATE.name())) {
return checks.response;
}
event.detail(Details.CODE_ID, code);
@@ -383,7 +380,7 @@ public class LoginActionsService {
@QueryParam("execution") String execution) {
event.event(EventType.REGISTER);
Checks checks = new Checks();
- if (!checks.verifyCode(DefaultAuthenticationFlows.REGISTRATION_FLOW, code, ClientSessionModel.Action.AUTHENTICATE.name())) {
+ if (!checks.verifyCode(realm.getRegistrationFlow(), code, ClientSessionModel.Action.AUTHENTICATE.name())) {
return checks.response;
}
if (!realm.isRegistrationAllowed()) {
@@ -484,7 +481,7 @@ public class LoginActionsService {
final MultivaluedMap formData) {
event.event(EventType.UPDATE_PROFILE);
Checks checks = new Checks();
- if (!checks.verifyCode(DefaultAuthenticationFlows.BROWSER_FLOW, code, ClientSessionModel.Action.UPDATE_PROFILE.name())) {
+ if (!checks.verifyCode(realm.getBrowserFlow(), code, ClientSessionModel.Action.UPDATE_PROFILE.name())) {
return checks.response;
}
ClientSessionCode accessCode = checks.clientCode;
@@ -546,7 +543,7 @@ public class LoginActionsService {
final MultivaluedMap formData) {
event.event(EventType.UPDATE_TOTP);
Checks checks = new Checks();
- if (!checks.verifyCode(DefaultAuthenticationFlows.BROWSER_FLOW, code, ClientSessionModel.Action.CONFIGURE_TOTP.name())) {
+ if (!checks.verifyCode(realm.getBrowserFlow(), code, ClientSessionModel.Action.CONFIGURE_TOTP.name())) {
return checks.response;
}
ClientSessionCode accessCode = checks.clientCode;
@@ -598,7 +595,7 @@ public class LoginActionsService {
final MultivaluedMap formData) {
event.event(EventType.UPDATE_PASSWORD);
Checks checks = new Checks();
- if (!checks.verifyCode(DefaultAuthenticationFlows.BROWSER_FLOW, code, ClientSessionModel.Action.UPDATE_PASSWORD.name(), ClientSessionModel.Action.RECOVER_PASSWORD.name())) {
+ if (!checks.verifyCode(realm.getBrowserFlow(), code, ClientSessionModel.Action.UPDATE_PASSWORD.name(), ClientSessionModel.Action.RECOVER_PASSWORD.name())) {
return checks.response;
}
ClientSessionCode accessCode = checks.clientCode;
@@ -661,7 +658,7 @@ public class LoginActionsService {
event.event(EventType.VERIFY_EMAIL);
if (key != null) {
Checks checks = new Checks();
- if (!checks.verifyCode(DefaultAuthenticationFlows.BROWSER_FLOW, key, ClientSessionModel.Action.VERIFY_EMAIL.name())) {
+ if (!checks.verifyCode(realm.getBrowserFlow(), key, ClientSessionModel.Action.VERIFY_EMAIL.name())) {
return checks.response;
}
ClientSessionCode accessCode = checks.clientCode;
@@ -688,7 +685,7 @@ public class LoginActionsService {
return AuthenticationManager.nextActionAfterAuthentication(session, userSession, clientSession, clientConnection, request, uriInfo, event);
} else {
Checks checks = new Checks();
- if (!checks.verifyCode(DefaultAuthenticationFlows.BROWSER_FLOW, code, ClientSessionModel.Action.VERIFY_EMAIL.name())) {
+ if (!checks.verifyCode(realm.getBrowserFlow(), code, ClientSessionModel.Action.VERIFY_EMAIL.name())) {
return checks.response;
}
ClientSessionCode accessCode = checks.clientCode;
@@ -711,7 +708,7 @@ public class LoginActionsService {
event.event(EventType.RESET_PASSWORD);
if (key != null) {
Checks checks = new Checks();
- if (!checks.verifyCode(DefaultAuthenticationFlows.BROWSER_FLOW, key, ClientSessionModel.Action.RECOVER_PASSWORD.name())) {
+ if (!checks.verifyCode(realm.getBrowserFlow(), key, ClientSessionModel.Action.RECOVER_PASSWORD.name())) {
return checks.response;
}
ClientSessionCode accessCode = checks.clientCode;
@@ -732,7 +729,7 @@ public class LoginActionsService {
final MultivaluedMap formData) {
event.event(EventType.SEND_RESET_PASSWORD);
Checks checks = new Checks();
- if (!checks.verifyCode(DefaultAuthenticationFlows.BROWSER_FLOW, code)) {
+ if (!checks.verifyCode(realm.getBrowserFlow(), code)) {
return checks.response;
}
final ClientSessionCode accessCode = checks.clientCode;
@@ -850,7 +847,7 @@ public class LoginActionsService {
throw new WebApplicationException(ErrorPage.error(session, Messages.INVALID_CODE));
}
Checks checks = new Checks();
- if (!checks.verifyCode(DefaultAuthenticationFlows.BROWSER_FLOW, code, action)) {
+ if (!checks.verifyCode(realm.getBrowserFlow(), code, action)) {
return checks.response;
}
final ClientSessionCode clientCode = checks.clientCode;
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/CustomFlowTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/CustomFlowTest.java
new file mode 100755
index 0000000000..731a3f1176
--- /dev/null
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/CustomFlowTest.java
@@ -0,0 +1,208 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2012, Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.keycloak.testsuite.forms;
+
+import org.junit.Assert;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.keycloak.OAuth2Constants;
+import org.keycloak.events.Details;
+import org.keycloak.events.Event;
+import org.keycloak.events.EventType;
+import org.keycloak.models.AuthenticationExecutionModel;
+import org.keycloak.models.AuthenticationFlowModel;
+import org.keycloak.models.BrowserSecurityHeaders;
+import org.keycloak.models.PasswordPolicy;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserCredentialModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.representations.AccessToken;
+import org.keycloak.representations.RefreshToken;
+import org.keycloak.representations.idm.CredentialRepresentation;
+import org.keycloak.services.managers.RealmManager;
+import org.keycloak.testsuite.AssertEvents;
+import org.keycloak.testsuite.OAuthClient;
+import org.keycloak.testsuite.pages.AppPage;
+import org.keycloak.testsuite.pages.AppPage.RequestType;
+import org.keycloak.testsuite.pages.ErrorPage;
+import org.keycloak.testsuite.pages.LoginPage;
+import org.keycloak.testsuite.pages.LoginPasswordUpdatePage;
+import org.keycloak.testsuite.pages.RegisterPage;
+import org.keycloak.testsuite.rule.KeycloakRule;
+import org.keycloak.testsuite.rule.WebResource;
+import org.keycloak.testsuite.rule.WebRule;
+import org.keycloak.util.Time;
+import org.openqa.selenium.WebDriver;
+
+import javax.ws.rs.client.Client;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.core.Response;
+import java.util.Map;
+
+import static org.junit.Assert.*;
+import static org.junit.Assert.assertEquals;
+
+/**
+ * @author Stian Thorgersen
+ */
+public class CustomFlowTest {
+
+ @ClassRule
+ public static KeycloakRule keycloakRule = new KeycloakRule(new KeycloakRule.KeycloakSetup() {
+ @Override
+ public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+ UserModel user = manager.getSession().users().addUser(appRealm, "login-test");
+ user.setEmail("login@test.com");
+ user.setEnabled(true);
+
+ userId = user.getId();
+
+ AuthenticationFlowModel flow = new AuthenticationFlowModel();
+ flow.setAlias("dummy");
+ flow.setDescription("dummy pass through flow");
+ flow.setProviderId("basic-flow");
+ flow.setTopLevel(true);
+ flow.setBuiltIn(false);
+ flow = appRealm.addAuthenticationFlow(flow);
+ appRealm.setBrowserFlow(flow);
+ appRealm.setDirectGrantFlow(flow);
+
+ AuthenticationExecutionModel execution;
+
+ execution = new AuthenticationExecutionModel();
+ execution.setParentFlow(flow.getId());
+ execution.setRequirement(AuthenticationExecutionModel.Requirement.REQUIRED);
+ execution.setAuthenticator(PassThroughAuthenticator.PROVIDER_ID);
+ execution.setPriority(10);
+ execution.setAuthenticatorFlow(false);
+ appRealm.addAuthenticatorExecution(execution);
+
+
+
+ flow = new AuthenticationFlowModel();
+ flow.setAlias("dummy registration");
+ flow.setDescription("dummy pass through registration");
+ flow.setProviderId("basic-flow");
+ flow.setTopLevel(true);
+ flow.setBuiltIn(false);
+ flow = appRealm.addAuthenticationFlow(flow);
+ appRealm.setRegistrationFlow(flow);
+
+ execution = new AuthenticationExecutionModel();
+ execution.setParentFlow(flow.getId());
+ execution.setRequirement(AuthenticationExecutionModel.Requirement.REQUIRED);
+ execution.setAuthenticator(PassThroughRegistration.PROVIDER_ID);
+ execution.setPriority(10);
+ execution.setAuthenticatorFlow(false);
+ appRealm.addAuthenticatorExecution(execution);
+
+
+ }
+ });
+
+ @Rule
+ public AssertEvents events = new AssertEvents(keycloakRule);
+
+ @Rule
+ public WebRule webRule = new WebRule(this);
+
+ @WebResource
+ protected OAuthClient oauth;
+
+ @WebResource
+ protected WebDriver driver;
+
+ @WebResource
+ protected AppPage appPage;
+
+ @WebResource
+ protected LoginPage loginPage;
+
+ @WebResource
+ protected ErrorPage errorPage;
+
+ @WebResource
+ protected LoginPasswordUpdatePage updatePasswordPage;
+
+ @WebResource
+ protected RegisterPage registerPage;
+
+ private static String userId;
+
+ @Test
+ public void loginSuccess() {
+
+ PassThroughAuthenticator.username = "login-test";
+
+ oauth.openLoginForm();
+
+ Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
+ Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE));
+
+ events.expectLogin().user(userId).detail(Details.USERNAME, "login-test").assertEvent();
+ }
+
+ @Test
+ public void grantTest() throws Exception {
+ PassThroughAuthenticator.username = "login-test";
+ grantAccessToken("login-test");
+ }
+
+
+ private void grantAccessToken(String login) throws Exception {
+
+ OAuthClient.AccessTokenResponse response = oauth.doGrantAccessTokenRequest("password", login, "password");
+
+ assertEquals(200, response.getStatusCode());
+
+ AccessToken accessToken = oauth.verifyToken(response.getAccessToken());
+ RefreshToken refreshToken = oauth.verifyRefreshToken(response.getRefreshToken());
+
+ events.expectLogin()
+ .client("test-app")
+ .user(userId)
+ .session(accessToken.getSessionState())
+ .detail(Details.RESPONSE_TYPE, "token")
+ .detail(Details.TOKEN_ID, accessToken.getId())
+ .detail(Details.REFRESH_TOKEN_ID, refreshToken.getId())
+ .detail(Details.USERNAME, login)
+ .removeDetail(Details.CODE_ID)
+ .removeDetail(Details.REDIRECT_URI)
+ .removeDetail(Details.CONSENT)
+ .assertEvent();
+
+ assertEquals(accessToken.getSessionState(), refreshToken.getSessionState());
+
+ OAuthClient.AccessTokenResponse refreshedResponse = oauth.doRefreshTokenRequest(response.getRefreshToken(), "password");
+
+ AccessToken refreshedAccessToken = oauth.verifyToken(refreshedResponse.getAccessToken());
+ RefreshToken refreshedRefreshToken = oauth.verifyRefreshToken(refreshedResponse.getRefreshToken());
+
+ assertEquals(accessToken.getSessionState(), refreshedAccessToken.getSessionState());
+ assertEquals(accessToken.getSessionState(), refreshedRefreshToken.getSessionState());
+
+ events.expectRefresh(refreshToken.getId(), refreshToken.getSessionState()).user(userId).client("test-app").assertEvent();
+ }
+
+
+}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/CustomRegistrationFlowTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/CustomRegistrationFlowTest.java
new file mode 100755
index 0000000000..8ab67d9194
--- /dev/null
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/CustomRegistrationFlowTest.java
@@ -0,0 +1,125 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2012, Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.keycloak.testsuite.forms;
+
+import org.junit.Assert;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.keycloak.OAuth2Constants;
+import org.keycloak.events.Details;
+import org.keycloak.models.AuthenticationExecutionModel;
+import org.keycloak.models.AuthenticationFlowModel;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.representations.AccessToken;
+import org.keycloak.representations.RefreshToken;
+import org.keycloak.services.managers.RealmManager;
+import org.keycloak.testsuite.AssertEvents;
+import org.keycloak.testsuite.OAuthClient;
+import org.keycloak.testsuite.pages.AppPage;
+import org.keycloak.testsuite.pages.AppPage.RequestType;
+import org.keycloak.testsuite.pages.ErrorPage;
+import org.keycloak.testsuite.pages.LoginPage;
+import org.keycloak.testsuite.pages.LoginPasswordUpdatePage;
+import org.keycloak.testsuite.pages.RegisterPage;
+import org.keycloak.testsuite.rule.KeycloakRule;
+import org.keycloak.testsuite.rule.WebResource;
+import org.keycloak.testsuite.rule.WebRule;
+import org.openqa.selenium.WebDriver;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * @author Stian Thorgersen
+ */
+public class CustomRegistrationFlowTest {
+
+ @ClassRule
+ public static KeycloakRule keycloakRule = new KeycloakRule(new KeycloakRule.KeycloakSetup() {
+ @Override
+ public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+ AuthenticationFlowModel flow = new AuthenticationFlowModel();
+ flow.setAlias("dummy registration");
+ flow.setDescription("dummy pass through registration");
+ flow.setProviderId("basic-flow");
+ flow.setTopLevel(true);
+ flow.setBuiltIn(false);
+ flow = appRealm.addAuthenticationFlow(flow);
+ appRealm.setRegistrationFlow(flow);
+
+ AuthenticationExecutionModel execution = new AuthenticationExecutionModel();
+ execution.setParentFlow(flow.getId());
+ execution.setRequirement(AuthenticationExecutionModel.Requirement.REQUIRED);
+ execution.setAuthenticator(PassThroughRegistration.PROVIDER_ID);
+ execution.setPriority(10);
+ execution.setAuthenticatorFlow(false);
+ appRealm.addAuthenticatorExecution(execution);
+
+
+ }
+ });
+
+ @Rule
+ public AssertEvents events = new AssertEvents(keycloakRule);
+
+ @Rule
+ public WebRule webRule = new WebRule(this);
+
+ @WebResource
+ protected OAuthClient oauth;
+
+ @WebResource
+ protected WebDriver driver;
+
+ @WebResource
+ protected AppPage appPage;
+
+ @WebResource
+ protected LoginPage loginPage;
+
+ @WebResource
+ protected ErrorPage errorPage;
+
+ @WebResource
+ protected LoginPasswordUpdatePage updatePasswordPage;
+
+ @WebResource
+ protected RegisterPage registerPage;
+
+ private static String userId;
+
+ @Test
+ public void registerUserSuccess() {
+ loginPage.open();
+ loginPage.clickRegister();
+
+ Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
+
+ String userId = events.expectRegister(PassThroughRegistration.username, PassThroughRegistration.email).assertEvent().getUserId();
+ events.expectLogin().detail("username", PassThroughRegistration.username).user(userId).assertEvent();
+ }
+
+
+
+
+}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/PassThroughAuthenticator.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/PassThroughAuthenticator.java
new file mode 100755
index 0000000000..264eaefaf1
--- /dev/null
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/PassThroughAuthenticator.java
@@ -0,0 +1,125 @@
+package org.keycloak.testsuite.forms;
+
+import org.keycloak.Config;
+import org.keycloak.authentication.AuthenticationProcessor;
+import org.keycloak.authentication.Authenticator;
+import org.keycloak.authentication.AuthenticatorContext;
+import org.keycloak.authentication.AuthenticatorFactory;
+import org.keycloak.models.AuthenticationExecutionModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.provider.ProviderConfigProperty;
+
+import java.util.List;
+
+/**
+ * @author Bill Burke
+ * @version $Revision: 1 $
+ */
+public class PassThroughAuthenticator implements Authenticator, AuthenticatorFactory {
+ public static final String PROVIDER_ID = "dummy-passthrough";
+ public static String username = "test-user@localhost";
+
+ @Override
+ public void authenticate(AuthenticatorContext context) {
+ UserModel user = KeycloakModelUtils.findUserByNameOrEmail(context.getSession(), context.getRealm(), username);
+ if (user == null) {
+ context.failure(AuthenticationProcessor.Error.UNKNOWN_USER);
+ return;
+ }
+ context.setUser(user);
+ context.success();
+ }
+
+ @Override
+ public boolean requiresUser() {
+ return false;
+ }
+
+ @Override
+ public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) {
+ return true;
+ }
+
+ @Override
+ public void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user) {
+
+ }
+
+ @Override
+ public void action(AuthenticatorContext context) {
+
+ }
+
+ @Override
+ public Authenticator create() {
+ return this;
+ }
+
+ @Override
+ public String getDisplayType() {
+ return "Dummy Pass Thru";
+ }
+
+ @Override
+ public String getReferenceCategory() {
+ return null;
+ }
+
+ @Override
+ public boolean isConfigurable() {
+ return false;
+ }
+
+ public static final AuthenticationExecutionModel.Requirement[] REQUIREMENT_CHOICES = {
+ AuthenticationExecutionModel.Requirement.REQUIRED
+ };
+
+ @Override
+ public AuthenticationExecutionModel.Requirement[] getRequirementChoices() {
+ return REQUIREMENT_CHOICES;
+ }
+
+ @Override
+ public boolean isUserSetupAllowed() {
+ return false;
+ }
+
+ @Override
+ public String getHelpText() {
+ return "Dummy authenticator. Just passes through and is hardcoded to a specific user";
+ }
+
+ @Override
+ public List getConfigProperties() {
+ return null;
+ }
+
+ @Override
+ public void close() {
+
+ }
+
+ @Override
+ public Authenticator create(KeycloakSession session) {
+ return this;
+ }
+
+ @Override
+ public void init(Config.Scope config) {
+
+ }
+
+ @Override
+ public void postInit(KeycloakSessionFactory factory) {
+
+ }
+
+ @Override
+ public String getId() {
+ return PROVIDER_ID;
+ }
+}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/PassThroughRegistration.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/PassThroughRegistration.java
new file mode 100755
index 0000000000..6f8498fb04
--- /dev/null
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/PassThroughRegistration.java
@@ -0,0 +1,144 @@
+package org.keycloak.testsuite.forms;
+
+import org.keycloak.Config;
+import org.keycloak.authentication.AuthenticationProcessor;
+import org.keycloak.authentication.Authenticator;
+import org.keycloak.authentication.AuthenticatorContext;
+import org.keycloak.authentication.AuthenticatorFactory;
+import org.keycloak.events.Details;
+import org.keycloak.events.EventType;
+import org.keycloak.models.AuthenticationExecutionModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.protocol.oidc.OIDCLoginProtocol;
+import org.keycloak.provider.ProviderConfigProperty;
+import org.keycloak.services.resources.AttributeFormDataProcessor;
+
+import java.util.List;
+
+/**
+ * @author Bill Burke
+ * @version $Revision: 1 $
+ */
+public class PassThroughRegistration implements Authenticator, AuthenticatorFactory {
+ public static final String PROVIDER_ID = "dummy-registration";
+ public static String username = "new-user@localhost";
+ public static String email = "new-user@localhost";
+
+ @Override
+ public void authenticate(AuthenticatorContext context) {
+ context.getEvent().detail(Details.USERNAME, username)
+ .detail(Details.REGISTER_METHOD, "form")
+ .detail(Details.EMAIL, email)
+ ;
+ UserModel user = context.getSession().users().addUser(context.getRealm(), username);
+ user.setEnabled(true);
+
+ user.setEmail(email);
+ context.getClientSession().setNote(OIDCLoginProtocol.LOGIN_HINT_PARAM, username);
+ context.setUser(user);
+ context.getEvent().user(user);
+ context.getEvent().success();
+ context.newEvent().event(EventType.LOGIN);
+ context.getEvent().client(context.getClientSession().getClient().getClientId())
+ .detail(Details.REDIRECT_URI, context.getClientSession().getRedirectUri())
+ .detail(Details.AUTH_METHOD, context.getClientSession().getAuthMethod());
+ String authType = context.getClientSession().getNote(Details.AUTH_TYPE);
+ if (authType != null) {
+ context.getEvent().detail(Details.AUTH_TYPE, authType);
+ }
+ context.success();
+ }
+
+ @Override
+ public boolean requiresUser() {
+ return false;
+ }
+
+ @Override
+ public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) {
+ return true;
+ }
+
+ @Override
+ public void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user) {
+
+ }
+
+ @Override
+ public void action(AuthenticatorContext context) {
+
+ }
+
+ @Override
+ public Authenticator create() {
+ return this;
+ }
+
+ @Override
+ public String getDisplayType() {
+ return "Dummy Pass Thru";
+ }
+
+ @Override
+ public String getReferenceCategory() {
+ return null;
+ }
+
+ @Override
+ public boolean isConfigurable() {
+ return false;
+ }
+
+ public static final AuthenticationExecutionModel.Requirement[] REQUIREMENT_CHOICES = {
+ AuthenticationExecutionModel.Requirement.REQUIRED
+ };
+
+ @Override
+ public AuthenticationExecutionModel.Requirement[] getRequirementChoices() {
+ return REQUIREMENT_CHOICES;
+ }
+
+ @Override
+ public boolean isUserSetupAllowed() {
+ return false;
+ }
+
+ @Override
+ public String getHelpText() {
+ return "Dummy authenticator. Just passes through and is hardcoded to a specific user";
+ }
+
+ @Override
+ public List getConfigProperties() {
+ return null;
+ }
+
+ @Override
+ public void close() {
+
+ }
+
+ @Override
+ public Authenticator create(KeycloakSession session) {
+ return this;
+ }
+
+ @Override
+ public void init(Config.Scope config) {
+
+ }
+
+ @Override
+ public void postInit(KeycloakSessionFactory factory) {
+
+ }
+
+ @Override
+ public String getId() {
+ return PROVIDER_ID;
+ }
+}
diff --git a/testsuite/integration/src/test/resources/META-INF/services/org.keycloak.authentication.AuthenticatorFactory b/testsuite/integration/src/test/resources/META-INF/services/org.keycloak.authentication.AuthenticatorFactory
new file mode 100755
index 0000000000..61c9297d9d
--- /dev/null
+++ b/testsuite/integration/src/test/resources/META-INF/services/org.keycloak.authentication.AuthenticatorFactory
@@ -0,0 +1,2 @@
+org.keycloak.testsuite.forms.PassThroughAuthenticator
+org.keycloak.testsuite.forms.PassThroughRegistration
\ No newline at end of file