keycloak-aplcache

Merge pull request #1286 from patriot1burke/master auth

5/27/2015 12:27:10 PM

Details

diff --git a/model/api/src/main/java/org/keycloak/migration/MigrationModelManager.java b/model/api/src/main/java/org/keycloak/migration/MigrationModelManager.java
index 62dcd68..7f52ab3 100755
--- a/model/api/src/main/java/org/keycloak/migration/MigrationModelManager.java
+++ b/model/api/src/main/java/org/keycloak/migration/MigrationModelManager.java
@@ -1,6 +1,7 @@
 package org.keycloak.migration;
 
 import org.jboss.logging.Logger;
+import org.keycloak.migration.migrators.MigrateTo1_3_0_Beta1;
 import org.keycloak.migration.migrators.MigrationTo1_2_0_CR1;
 import org.keycloak.models.KeycloakSession;
 
@@ -24,6 +25,12 @@ public class MigrationModelManager {
             }
             new MigrationTo1_2_0_CR1().migrate(session);
         }
+        if (stored == null || stored.lessThan(MigrateTo1_3_0_Beta1.VERSION)) {
+            if (stored != null) {
+                logger.debug("Migrating older model to 1.3.0.Beta1 updates");
+            }
+            new MigrateTo1_3_0_Beta1().migrate(session);
+        }
 
         model.setStoredVersion(MigrationModel.LATEST_VERSION);
     }
diff --git a/model/api/src/main/java/org/keycloak/migration/migrators/MigrateTo1_3_0_Beta1.java b/model/api/src/main/java/org/keycloak/migration/migrators/MigrateTo1_3_0_Beta1.java
new file mode 100755
index 0000000..ccf3c75
--- /dev/null
+++ b/model/api/src/main/java/org/keycloak/migration/migrators/MigrateTo1_3_0_Beta1.java
@@ -0,0 +1,27 @@
+package org.keycloak.migration.migrators;
+
+import org.keycloak.migration.ModelVersion;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.utils.DefaultAuthenticationFlows;
+
+import java.util.List;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class MigrateTo1_3_0_Beta1 {
+    public static final ModelVersion VERSION = new ModelVersion("1.3.0.Beta1");
+
+
+    public void migrate(KeycloakSession session) {
+        List<RealmModel> realms = session.realms().getRealms();
+        for (RealmModel realm : realms) {
+            if (realm.getAuthenticationFlows().size() == 0) {
+                DefaultAuthenticationFlows.addFlows(realm);
+            }
+        }
+
+    }
+}
diff --git a/model/api/src/main/java/org/keycloak/models/AuthenticationExecutionModel.java b/model/api/src/main/java/org/keycloak/models/AuthenticationExecutionModel.java
index 8cea65b..79a2b67 100755
--- a/model/api/src/main/java/org/keycloak/models/AuthenticationExecutionModel.java
+++ b/model/api/src/main/java/org/keycloak/models/AuthenticationExecutionModel.java
@@ -78,6 +78,23 @@ public class AuthenticationExecutionModel {
     public enum Requirement {
         REQUIRED,
         OPTIONAL,
-        ALTERNATIVE
+        ALTERNATIVE,
+        DISABLED
+    }
+
+    public boolean isRequired() {
+        return requirement == Requirement.REQUIRED;
+    }
+    public boolean isOptional() {
+        return requirement == Requirement.OPTIONAL;
+    }
+    public boolean isAlternative() {
+        return requirement == Requirement.ALTERNATIVE;
+    }
+    public boolean isDisabled() {
+        return requirement == Requirement.DISABLED;
+    }
+    public boolean isEnabled() {
+        return requirement != Requirement.DISABLED;
     }
 }
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
new file mode 100755
index 0000000..05a5a4a
--- /dev/null
+++ b/model/api/src/main/java/org/keycloak/models/utils/DefaultAuthenticationFlows.java
@@ -0,0 +1,92 @@
+package org.keycloak.models.utils;
+
+import org.keycloak.models.AuthenticationExecutionModel;
+import org.keycloak.models.AuthenticationFlowModel;
+import org.keycloak.models.AuthenticatorModel;
+import org.keycloak.models.RealmModel;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class DefaultAuthenticationFlows {
+    public static void addFlows(RealmModel realm) {
+        AuthenticatorModel model = new AuthenticatorModel();
+        model.setProviderId("auth-cookie");
+        model.setAlias("Cookie");
+        AuthenticatorModel cookieAuth = realm.addAuthenticator(model);
+        model = new AuthenticatorModel();
+        model.setProviderId("auth-login-form-otp");
+        model.setAlias("Login Form OTP");
+        AuthenticatorModel loginFormOtp = realm.addAuthenticator(model);
+        model = new AuthenticatorModel();
+        model.setProviderId("auth-login-form-password");
+        model.setAlias("Login Form Password");
+        AuthenticatorModel password = realm.addAuthenticator(model);
+        model = new AuthenticatorModel();
+        model.setProviderId("auth-login-form-username");
+        model.setAlias("Login Form Username");
+        AuthenticatorModel username = realm.addAuthenticator(model);
+        model = new AuthenticatorModel();
+        model.setProviderId("auth-otp-form");
+        model.setAlias("Single OTP Form");
+        AuthenticatorModel otp = realm.addAuthenticator(model);
+
+        AuthenticationFlowModel browser = new AuthenticationFlowModel();
+        browser.setAlias("browser");
+        browser.setDescription("browser based authentication");
+        browser = realm.addAuthenticationFlow(browser);
+        AuthenticationExecutionModel execution = new AuthenticationExecutionModel();
+        execution.setParentFlow(browser.getId());
+        execution.setRequirement(AuthenticationExecutionModel.Requirement.ALTERNATIVE);
+        execution.setAuthenticator(cookieAuth.getId());
+        execution.setPriority(0);
+        execution.setUserSetupAllowed(false);
+        execution.setAutheticatorFlow(false);
+        realm.addAuthenticatorExecution(execution);
+        AuthenticationFlowModel forms = new AuthenticationFlowModel();
+        forms.setAlias("forms");
+        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.setUserSetupAllowed(false);
+        execution.setAutheticatorFlow(true);
+        realm.addAuthenticatorExecution(execution);
+
+        // forms
+        // Username processing
+        execution = new AuthenticationExecutionModel();
+        execution.setParentFlow(forms.getId());
+        execution.setRequirement(AuthenticationExecutionModel.Requirement.REQUIRED);
+        execution.setAuthenticator(username.getId());
+        execution.setPriority(10);
+        execution.setUserSetupAllowed(false);
+        execution.setAutheticatorFlow(false);
+        realm.addAuthenticatorExecution(execution);
+
+        // password processing
+        execution = new AuthenticationExecutionModel();
+        execution.setParentFlow(forms.getId());
+        execution.setRequirement(AuthenticationExecutionModel.Requirement.REQUIRED);
+        execution.setAuthenticator(password.getId());
+        execution.setPriority(11);
+        execution.setUserSetupAllowed(false);
+        execution.setAutheticatorFlow(false);
+        realm.addAuthenticatorExecution(execution);
+
+        // otp processing
+        execution = new AuthenticationExecutionModel();
+        execution.setParentFlow(forms.getId());
+        execution.setRequirement(AuthenticationExecutionModel.Requirement.OPTIONAL);
+        execution.setAuthenticator(otp.getId());
+        execution.setPriority(12);
+        execution.setUserSetupAllowed(true);
+        execution.setAutheticatorFlow(false);
+        realm.addAuthenticatorExecution(execution);
+
+    }
+}
diff --git a/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java b/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
index 5b20edf..5920e10 100755
--- a/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
+++ b/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
@@ -1,9 +1,11 @@
 package org.keycloak.authentication;
 
+import org.jboss.logging.Logger;
 import org.jboss.resteasy.spi.HttpRequest;
 import org.keycloak.ClientConnection;
 import org.keycloak.events.EventBuilder;
 import org.keycloak.models.AuthenticationExecutionModel;
+import org.keycloak.models.AuthenticationFlowModel;
 import org.keycloak.models.AuthenticatorModel;
 import org.keycloak.models.ClientSessionModel;
 import org.keycloak.models.KeycloakSession;
@@ -23,40 +25,18 @@ import java.util.Map;
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
  * @version $Revision: 1 $
  */
-
-//
-// setup
-// cookie: master, alternative
-// CERT_AUTH: alternative
-// UserPassword: alternative
-//    OTP: optional
-//    CAPTHA: required
-//
-// scenario: username password
-// * cookie, attempted
-// * cert, attempated
-// * usernamepassord, doesn't see form, sets challenge to form
-//
-//
-//
-//
-//
-//
-//
-
-
-
 public class AuthenticationProcessor {
+    protected static Logger logger = Logger.getLogger(AuthenticationProcessor.class);
     protected RealmModel realm;
     protected UserSessionModel userSession;
     protected ClientSessionModel clientSession;
     protected ClientConnection connection;
     protected UriInfo uriInfo;
     protected KeycloakSession session;
-    protected List<AuthenticationExecutionModel> executions;
     protected BruteForceProtector protector;
     protected EventBuilder eventBuilder;
     protected HttpRequest request;
+    protected String flowId;
 
 
     public static enum Status {
@@ -98,6 +78,50 @@ public class AuthenticationProcessor {
         return session;
     }
 
+    public AuthenticationProcessor setRealm(RealmModel realm) {
+        this.realm = realm;
+        return this;
+    }
+
+    public AuthenticationProcessor setClientSession(ClientSessionModel clientSession) {
+        this.clientSession = clientSession;
+        return this;
+    }
+
+    public AuthenticationProcessor setConnection(ClientConnection connection) {
+        this.connection = connection;
+        return this;
+    }
+
+    public AuthenticationProcessor setUriInfo(UriInfo uriInfo) {
+        this.uriInfo = uriInfo;
+        return this;
+    }
+
+    public AuthenticationProcessor setSession(KeycloakSession session) {
+        this.session = session;
+        return this;
+    }
+
+    public AuthenticationProcessor setProtector(BruteForceProtector protector) {
+        this.protector = protector;
+        return this;
+    }
+
+    public AuthenticationProcessor setEventBuilder(EventBuilder eventBuilder) {
+        this.eventBuilder = eventBuilder;
+        return this;
+    }
+
+    public AuthenticationProcessor setRequest(HttpRequest request) {
+        this.request = request;
+        return this;
+    }
+
+    public AuthenticationProcessor setFlowId(String flowId) {
+        this.flowId = flowId;
+        return this;
+    }
 
     private class Result implements AuthenticatorContext {
         AuthenticatorModel model;
@@ -261,35 +285,74 @@ public class AuthenticationProcessor {
 
     }
 
-    protected boolean isProcessed(UserSessionModel.AuthenticatorStatus status) {
+    protected boolean isProcessed(AuthenticationExecutionModel model) {
+        if (model.isDisabled()) return true;
+        UserSessionModel.AuthenticatorStatus status = clientSession.getAuthenticators().get(model.getId());
+        if (status == null) return false;
         return status == UserSessionModel.AuthenticatorStatus.SUCCESS || status == UserSessionModel.AuthenticatorStatus.SKIPPED
                 || status == UserSessionModel.AuthenticatorStatus.ATTEMPTED
                 || status == UserSessionModel.AuthenticatorStatus.SETUP_REQUIRED;
     }
 
-    public Response authenticate() {
+    public boolean isSuccessful(AuthenticationExecutionModel model) {
+        UserSessionModel.AuthenticatorStatus status = clientSession.getAuthenticators().get(model.getId());
+        if (status == null) return false;
+        return status == UserSessionModel.AuthenticatorStatus.SUCCESS;
+    }
+
+    public Response authenticate() throws AuthException {
         UserModel authUser = clientSession.getAuthenticatedUser();
         validateUser(authUser);
-        Response challenge = null;
-        Map<String, UserSessionModel.AuthenticatorStatus> previousAttempts = clientSession.getAuthenticators();
+        Response challenge = processFlow(flowId);
+        if (challenge != null) return challenge;
+        if (clientSession.getAuthenticatedUser() == null) {
+            throw new AuthException(Error.UNKNOWN_USER);
+        }
+        return authenticationComplete();
+
+    }
+
+    public Response processFlow(String flowId) {
+        AuthenticationFlowModel flow = realm.getAuthenticationFlowById(flowId);
+        if (flow == null) {
+            logger.error("Unknown flow to execute with");
+            throw new AuthException(Error.INTERNAL_ERROR);
+        }
+        List<AuthenticationExecutionModel> executions = realm.getAuthenticationExecutions(flowId);
+        if (executions == null) return null;
+        Response alternativeChallenge = null;
+        boolean alternativeSuccessful = false;
         for (AuthenticationExecutionModel model : executions) {
-            UserSessionModel.AuthenticatorStatus oldStatus = previousAttempts.get(model.getId());
-            if (isProcessed(oldStatus)) continue;
+            if (isProcessed(model)) {
+                if (!alternativeSuccessful && model.isAlternative() && isSuccessful(model)) alternativeSuccessful = true;
+                continue;
+            }
+            Result context = null;
+            if (model.isAlternative() && alternativeSuccessful) {
+                clientSession.setAuthenticatorStatus(model.getId(), UserSessionModel.AuthenticatorStatus.SKIPPED);
+                continue;
+            }
+            if (model.isAutheticatorFlow()) {
+                Response flowResponse = processFlow(model.getAuthenticator());
+                if (flowResponse == null) {
+                    clientSession.setAuthenticatorStatus(model.getId(), UserSessionModel.AuthenticatorStatus.SUCCESS);
+                    if (model.isAlternative()) alternativeSuccessful = true;
+                    continue;
+                } else {
+                    return flowResponse;
+                }
+
+            }
 
             AuthenticatorModel authenticatorModel = realm.getAuthenticatorById(model.getAuthenticator());
             AuthenticatorFactory factory = (AuthenticatorFactory)session.getKeycloakSessionFactory().getProviderFactory(Authenticator.class, authenticatorModel.getProviderId());
             Authenticator authenticator = factory.create(authenticatorModel);
+            UserModel authUser = clientSession.getAuthenticatedUser();
+
             if (authenticator.requiresUser() && authUser == null){
-                if ( authenticator.requiresUser()) {
-                    if (challenge != null) return challenge;
-                    throw new AuthException(Error.UNKNOWN_USER);
-                }
-            }
-            if (authUser != null && model.getRequirement() == AuthenticationExecutionModel.Requirement.ALTERNATIVE) {
-                clientSession.setAuthenticatorStatus(model.getId(), UserSessionModel.AuthenticatorStatus.SKIPPED);
-                continue;
+                if (alternativeChallenge != null) return alternativeChallenge;
+                throw new AuthException(Error.UNKNOWN_USER);
             }
-            authUser = clientSession.getAuthenticatedUser();
 
             if (authenticator.requiresUser() && authUser != null && !authenticator.configuredFor(authUser)) {
                 if (model.getRequirement() == AuthenticationExecutionModel.Requirement.REQUIRED) {
@@ -303,19 +366,21 @@ public class AuthenticationProcessor {
                 }
                 continue;
             }
-            Result context = new Result(authenticatorModel, authenticator);
+            context = new Result(authenticatorModel, authenticator);
             authenticator.authenticate(context);
             Status result = context.getStatus();
             if (result == Status.SUCCESS){
                 clientSession.setAuthenticatorStatus(model.getId(), UserSessionModel.AuthenticatorStatus.SUCCESS);
-                //if (model.isMasterAuthenticator()) return authenticationComplete();
+                if (model.isAlternative()) alternativeSuccessful = true;
                 continue;
             } else if (result == Status.FAILED) {
+                logUserFailure();
                 if (context.challenge != null) return context.challenge;
                 throw new AuthException(context.error);
             } else if (result == Status.CHALLENGE) {
-                if (model.getRequirement() == AuthenticationExecutionModel.Requirement.REQUIRED) return context.challenge;
-                if (challenge != null) challenge = context.challenge;
+                if (model.isRequired()) return context.challenge;
+                else if (model.isAlternative()) alternativeChallenge = context.challenge;
+                else clientSession.setAuthenticatorStatus(model.getId(), UserSessionModel.AuthenticatorStatus.SKIPPED);
                 continue;
             } else if (result == Status.FAILURE_CHALLENGE) {
                 logUserFailure();
@@ -326,14 +391,7 @@ public class AuthenticationProcessor {
                 continue;
             }
         }
-
-        if (authUser == null) {
-            if (challenge != null) return challenge;
-            throw new AuthException(Error.UNKNOWN_USER);
-        }
-
-
-        return authenticationComplete();
+        return null;
     }
 
 
@@ -349,7 +407,7 @@ public class AuthenticationProcessor {
         }
     }
 
-    protected  Response authenticationComplete() {
+    protected Response authenticationComplete() {
         if (userSession == null) { // if no authenticator attached a usersession
             userSession = session.sessions().createUserSession(realm, clientSession.getAuthenticatedUser(), clientSession.getAuthenticatedUser().getUsername(), connection.getRemoteAddr(), "form", false, null, null);
             userSession.setState(UserSessionModel.State.LOGGING_IN);
diff --git a/services/src/main/java/org/keycloak/services/managers/RealmManager.java b/services/src/main/java/org/keycloak/services/managers/RealmManager.java
index f2caef8..41f5ac6 100755
--- a/services/src/main/java/org/keycloak/services/managers/RealmManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/RealmManager.java
@@ -16,6 +16,7 @@ import org.keycloak.models.RoleModel;
 import org.keycloak.models.UserFederationProviderModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.models.UserSessionProvider;
+import org.keycloak.models.utils.DefaultAuthenticationFlows;
 import org.keycloak.models.utils.KeycloakModelUtils;
 import org.keycloak.models.utils.RepresentationToModel;
 import org.keycloak.representations.idm.ClientRepresentation;
@@ -86,10 +87,15 @@ public class RealmManager {
         setupAccountManagement(realm);
         setupBrokerService(realm);
         setupAdminConsole(realm);
+        setupAuthenticationFlows(realm);
 
         return realm;
     }
 
+    protected void setupAuthenticationFlows(RealmModel realm) {
+        if (realm.getAuthenticationFlows().size() == 0) DefaultAuthenticationFlows.addFlows(realm);
+    }
+
     protected void setupAdminConsole(RealmModel realm) {
         ClientModel adminConsole = realm.getClientByClientId(Constants.ADMIN_CONSOLE_CLIENT_ID);
         if (adminConsole == null) adminConsole = new ClientManager(this).createClient(realm, Constants.ADMIN_CONSOLE_CLIENT_ID);
@@ -254,6 +260,8 @@ public class RealmManager {
 
         RepresentationToModel.importRealm(session, rep, realm);
 
+        setupAuthenticationFlows(realm);
+
         // Refresh periodic sync tasks for configured federationProviders
         List<UserFederationProviderModel> federationProviders = realm.getUserFederationProviders();
         UsersSyncManager usersSyncManager = new UsersSyncManager();