keycloak-uncached
Changes
forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js 39(+39 -0)
forms/common-themes/src/main/resources/theme/base/admin/resources/partials/authentication-flows.html 64(+64 -0)
forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/FreeMarkerLoginFormsProvider.java 3(+3 -0)
services/src/main/java/org/keycloak/authentication/authenticators/CookieAuthenticatorFactory.java 19(+19 -0)
services/src/main/java/org/keycloak/authentication/authenticators/LoginFormOTPAuthenticatorFactory.java 23(+23 -0)
services/src/main/java/org/keycloak/authentication/authenticators/LoginFormPasswordAuthenticatorFactory.java 20(+20 -0)
services/src/main/java/org/keycloak/authentication/authenticators/LoginFormUsernameAuthenticatorFactory.java 22(+22 -0)
services/src/main/java/org/keycloak/authentication/authenticators/OTPFormAuthenticatorFactory.java 21(+21 -0)
services/src/main/java/org/keycloak/authentication/authenticators/SetRequiredActionAuthenticator.java 48(+0 -48)
services/src/main/java/org/keycloak/authentication/authenticators/SetRequiredActionAuthenticatorFactory.java 69(+0 -69)
services/src/main/java/org/keycloak/authentication/authenticators/SpnegoAuthenticator.java 81(+75 -6)
services/src/main/java/org/keycloak/authentication/authenticators/SpnegoAuthenticatorFactory.java 23(+23 -0)
services/src/main/java/org/keycloak/services/resources/admin/AuthenticationFlowResource.java 180(+180 -0)
services/src/main/resources/META-INF/services/org.keycloak.authentication.AuthenticatorFactory 1(+0 -1)
testsuite/integration/src/test/java/org/keycloak/testsuite/federation/AbstractKerberosTest.java 27(+23 -4)
testsuite/integration/src/test/java/org/keycloak/testsuite/federation/KerberosLdapTest.java 16(+15 -1)
Details
diff --git a/events/api/src/main/java/org/keycloak/events/EventBuilder.java b/events/api/src/main/java/org/keycloak/events/EventBuilder.java
old mode 100644
new mode 100755
index ee7a70a..641f233
--- a/events/api/src/main/java/org/keycloak/events/EventBuilder.java
+++ b/events/api/src/main/java/org/keycloak/events/EventBuilder.java
@@ -140,7 +140,9 @@ public class EventBuilder {
     }
 
     public void error(String error) {
-        event.setType(EventType.valueOf(event.getType().name() + "_ERROR"));
+        if (!event.getType().name().endsWith("_ERROR")) {
+            event.setType(EventType.valueOf(event.getType().name() + "_ERROR"));
+        }
         event.setError(error);
         send();
     }
                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 cc3092f..822e688 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
@@ -1045,6 +1045,16 @@ module.config([ '$routeProvider', function($routeProvider) {
             },
             controller : 'ProtocolListCtrl'
         })
+        .when('/realms/:realm/authentication', {
+            templateUrl : resourceUrl + '/partials/authentication-flows.html',
+            resolve : {
+                realm : function(RealmLoader) {
+                    return RealmLoader();
+                }
+            },
+            controller : 'AuthenticationFlowsCtrl'
+        })
+
         .when('/server-info', {
             templateUrl : resourceUrl + '/partials/server-info.html'
         })
                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 53ad7ae..a88ffd7 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
@@ -1572,6 +1572,45 @@ module.controller('IdentityProviderMapperCreateCtrl', function($scope, realm, id
 
 });
 
+module.controller('AuthenticationFlowsCtrl', function($scope, realm, AuthenticationExecutions, Notifications, Dialog, $location) {
+    $scope.realm = realm;
+    var setupForm = function() {
+        AuthenticationExecutions.query({realm: realm.realm, alias: 'browser'}, function(data) {
+            $scope.executions = data;
+            $scope.flowmax = 0;
+            for (var i = 0; i < $scope.executions.length; i++ ) {
+                execution = $scope.executions[i];
+                if (execution.requirementChoices.length > $scope.flowmax) {
+                    $scope.flowmax = execution.requirementChoices.length;
+                }
+            }
+            for (var i = 0; i < $scope.executions.length; i++ ) {
+                execution = $scope.executions[i];
+                execution.empties = [];
+                for (j = 0; j < $scope.flowmax - execution.requirementChoices.length; j++) {
+                    execution.empties.push(j);
+                }
+            }
+        })
+    };
+
+    $scope.updateExecution = function(execution) {
+        var copy = angular.copy(execution);
+        delete copy.empties;
+        AuthenticationExecutions.update({realm: realm.realm, alias: 'browser'}, copy, function() {
+            Notifications.success("Auth requirement updated");
+            setupForm();
+        });
+
+    };
+
+
+    setupForm();
+
+
+});
+
+
 
 
 
                diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/services.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/services.js
index e9f877b..067bb20 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/services.js
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/services.js
@@ -1073,3 +1073,15 @@ module.factory('IdentityProviderMapper', function($resource) {
     });
 });
 
+module.factory('AuthenticationExecutions', function($resource) {
+    return $resource(authUrl + '/admin/realms/:realm/authentication-flows/flow/:alias/executions', {
+        realm : '@realm',
+        alias : '@alias'
+    }, {
+        update : {
+            method : 'PUT'
+        }
+    });
+});
+
+
                diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/authentication-flows.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/authentication-flows.html
new file mode 100755
index 0000000..b3ecf71
--- /dev/null
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/authentication-flows.html
@@ -0,0 +1,64 @@
+<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
+    <h1><strong>Authentication Flows</strong> {{realm.realm|capitalize}}</h1>
+
+    <table class="table table-striped table-bordered">
+        <thead>
+        <!--
+        <tr>
+            <th class="kc-table-actions" colspan="5">
+                <div class="form-inline">
+                    <div class="form-group">
+                        <div class="input-group">
+                            <input type="text" placeholder="Search..." data-ng-model="search.name" class="form-control search" onkeyup="if(event.keyCode == 13){$(this).next('I').click();}">
+                            <div class="input-group-addon">
+                                <i class="fa fa-search" type="submit"></i>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </th>
+        </tr>
+        -->
+        <tr data-ng-hide="executions.length == 0">
+            <th colspan="2">Auth Type</th>
+            <th colspan="{{flowmax}}">Requirement</th>
+        </tr>
+        </thead>
+        <tbody>
+        <tr ng-repeat="execution in executions">
+            <td ng-show="execution.subFlow"></td>
+            <td><h2>{{execution.referenceType}}</h2></td>
+            <td ng-hide="execution.subFlow"></td>
+            <td ng-repeat="choice in execution.requirementChoices">
+                <!--
+                <div class="dropdown pull-left">
+                <select class="form-control"
+                        ng-model="execution.requirement"
+                        ng-options="choice for choice in execution.requirementChoices">
+                </select>
+                </div>
+                -->
+                <!--
+                <div ng-repeat="choice in execution.requirementChoices">
+                <label >
+                    <input type="radio" ng-model="execution.requirement" ng-value="choice">
+                    {{choice}}
+                </label>
+                </div>
+                -->
+            <label >
+                <input type="radio" ng-model="execution.requirement" ng-value="choice" ng-change="updateExecution(execution)">
+                {{choice}}
+            </label>
+
+            </td>
+            <td ng-repeat="emptee in execution.empties"></td>
+         </tr>
+        <tr data-ng-show="executions.length == 0">
+            <td>No executions available</td>
+        </tr>
+        </tbody>
+    </table>
+</div>
+
+<kc-menu></kc-menu>
\ No newline at end of file
                diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/templates/kc-menu.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/templates/kc-menu.html
index f5521ad..ba09d84 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/templates/kc-menu.html
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/templates/kc-menu.html
@@ -16,6 +16,7 @@
             <li data-ng-show="access.viewRealm" data-ng-class="(path[2] == 'roles' || path[2] == 'default-roles' || (path[1] == 'role' && path[3] != 'clients')) && 'active'"><a href="#/realms/{{realm.realm}}/roles">Roles</a></li>
             <li data-ng-show="access.viewRealm" data-ng-class="(path[2] == 'identity-provider-settings' || path[2] == 'identity-provider-mappers') && 'active'"><a href="#/realms/{{realm.realm}}/identity-provider-settings">Identity Providers</a></li>
             <li data-ng-show="access.viewRealm" data-ng-class="(path[1] == 'user-federation' || path[2] == 'user-federation') && 'active'"><a href="#/realms/{{realm.realm}}/user-federation">User Federation</a></li>
+            <li data-ng-show="access.viewRealm" data-ng-class="(path[1] == 'authentication' || path[2] == 'authentication') && 'active'"><a href="#/realms/{{realm.realm}}/authentication">Authentication</a></li>
         </ul>
     </div>
 
                diff --git a/forms/common-themes/src/main/resources/theme/base/login/bypass_kerberos.ftl b/forms/common-themes/src/main/resources/theme/base/login/bypass_kerberos.ftl
new file mode 100755
index 0000000..d87163c
--- /dev/null
+++ b/forms/common-themes/src/main/resources/theme/base/login/bypass_kerberos.ftl
@@ -0,0 +1,25 @@
+<#import "template.ftl" as layout>
+<@layout.registrationLayout displayMessage=false; section>
+    <#if section = "title">
+    ${msg("kerberosNotConfiguredTitle")}
+    <#elseif section = "header">
+    ${msg("kerberosNotConfigured")}
+    <#elseif section = "form">
+    <div id="kc-info-message">
+        <!-- <h3>${msg("kerberosNotConfigured")}</h3> -->
+        <p class="instruction">${msg("bypassKerberosDetail")}</p>
+        <form class="form-actions" action="${url.loginAction}" method="POST">
+            <div class="${properties.kcFormGroupClass!}">
+                 <div id="kc-form-buttons" class="${properties.kcFormButtonsClass!}">
+                    <div class="${properties.kcFormButtonsWrapperClass!}">
+                        <input class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonLargeClass!}" name="continue" id="kc-login" type="submit" value="${msg("doContinue")}"/>
+                    </div>
+                </div>
+                <#if client?? && client.baseUrl?has_content>
+                    <p><a href="${client.baseUrl}">${msg("backToApplication")}</a></p>
+                </#if>
+            </div>
+        </form>
+    </div>
+    </#if>
+</@layout.registrationLayout>
\ No newline at end of file
                diff --git a/forms/common-themes/src/main/resources/theme/base/login/messages/messages_de.properties b/forms/common-themes/src/main/resources/theme/base/login/messages/messages_de.properties
index 347ad36..6cbb05d 100755
--- a/forms/common-themes/src/main/resources/theme/base/login/messages/messages_de.properties
+++ b/forms/common-themes/src/main/resources/theme/base/login/messages/messages_de.properties
@@ -6,8 +6,13 @@ doYes=Ja
 doNo=Nein
 doAccept=Accept
 doDecline=Decline
+doContinue=Continue
 doForgotPassword=Passwort vergessen?
 doClickHere=hier klicken
+kerberosNotConfigured=Kerberos Not Configured
+kerberosNotConfiguredTitle=Kerberos Not Configured
+bypassKerberos=Your browser is not set up for Kerberos login.  Please click continue to login in through other means
+kerberosNotSetUp=Kerberos is not set up.  You cannot login.
 
 registerWithTitle=Registrierung bei {0}
 registerWithTitleHtml=Registrierung bei <strong>{0}</strong>
                diff --git a/forms/common-themes/src/main/resources/theme/base/login/messages/messages_en.properties b/forms/common-themes/src/main/resources/theme/base/login/messages/messages_en.properties
index 8aac702..7a5237d 100755
--- a/forms/common-themes/src/main/resources/theme/base/login/messages/messages_en.properties
+++ b/forms/common-themes/src/main/resources/theme/base/login/messages/messages_en.properties
@@ -4,11 +4,15 @@ doCancel=Cancel
 doSubmit=Submit
 doYes=Yes
 doNo=No
+doContinue=Continue
 doAccept=Accept
 doDecline=Decline
 doForgotPassword=Forgot Password?
 doClickHere=Click here
-
+kerberosNotConfigured=Kerberos Not Configured
+kerberosNotConfiguredTitle=Kerberos Not Configured
+bypassKerberosDetail=Either you are not logged in via Kerberos or your browser is not set up for Kerberos login.  Please click continue to login in through other means
+kerberosNotSetUp=Kerberos is not set up.  You cannot login.
 registerWithTitle=Register with {0}
 registerWithTitleHtml=Register with <strong>{0}</strong>
 loginTitle=Log in to {0}
                diff --git a/forms/common-themes/src/main/resources/theme/base/login/messages/messages_it.properties b/forms/common-themes/src/main/resources/theme/base/login/messages/messages_it.properties
index 0fb25d7..5049b68 100755
--- a/forms/common-themes/src/main/resources/theme/base/login/messages/messages_it.properties
+++ b/forms/common-themes/src/main/resources/theme/base/login/messages/messages_it.properties
@@ -6,8 +6,13 @@ doYes=Si
 doNo=No
 doAccept=Accept
 doDecline=Decline
+doContinue=Continue
 doForgotPassword=Password Dimenticata?
 doClickHere=Clicca qui
+bypassKerberos=Your browser is not set up for Kerberos login.  Please click continue to login in through other means
+kerberosNotSetUp=Kerberos is not set up.  You cannot login.
+kerberosNotConfigured=Kerberos Not Configured
+kerberosNotConfiguredTitle=Kerberos Not Configured
 
 registerWithTitle=Registrati come {0}
 registerWithTitleHtml=Registrati come <strong>{0}</strong>
                diff --git a/forms/common-themes/src/main/resources/theme/base/login/messages/messages_pt_BR.properties b/forms/common-themes/src/main/resources/theme/base/login/messages/messages_pt_BR.properties
index a828f72..4959003 100755
--- a/forms/common-themes/src/main/resources/theme/base/login/messages/messages_pt_BR.properties
+++ b/forms/common-themes/src/main/resources/theme/base/login/messages/messages_pt_BR.properties
@@ -6,8 +6,13 @@ doYes=Sim
 doAccept=Accept
 doDecline=Decline
 doNo=N\u00E3o
+doContinue=Continue
 doForgotPassword=Esqueceu sua senha?
 doClickHere=Clique aqui
+bypassKerberos=Your browser is not set up for Kerberos login.  Please click continue to login in through other means
+kerberosNotSetUp=Kerberos is not set up.  You cannot login.
+kerberosNotConfigured=Kerberos Not Configured
+kerberosNotConfiguredTitle=Kerberos Not Configured
 
 registerWithTitle=Registre-se com {0}
 registerWithTitleHtml=Registre-se com <strong>{0}</strong>
                diff --git a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/FreeMarkerLoginFormsProvider.java b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/FreeMarkerLoginFormsProvider.java
index 0b36b68..413c701 100755
--- a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/FreeMarkerLoginFormsProvider.java
+++ b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/FreeMarkerLoginFormsProvider.java
@@ -314,6 +314,9 @@ import java.util.concurrent.TimeUnit;
         } catch (IOException e) {
             logger.warn("Failed to load properties", e);
         }
+        if (client != null) {
+            attributes.put("client", new ClientBean(client));
+        }
 
         Properties messagesBundle;
         Locale locale = LocaleHelper.getLocale(realm, user, uriInfo, session.getContext().getRequestHeaders());
                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 73200e8..61cc52b 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
@@ -1289,6 +1289,7 @@ public class RealmAdapter implements RealmModel {
             AuthenticationExecutionModel model = entityToModel(entity);
             executions.add(model);
         }
+        Collections.sort(executions, AuthenticationExecutionModel.ExecutionComparator.SINGLETON);
         return executions;
     }
 
                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 ecf399a..9978aba 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
@@ -1588,6 +1588,7 @@ public class RealmAdapter implements RealmModel {
             AuthenticationExecutionModel model = entityToModel(entity);
             executions.add(model);
         }
+        Collections.sort(executions, AuthenticationExecutionModel.ExecutionComparator.SINGLETON);
         return executions;
     }
 
                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 2b6b85b..4b8090c 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
@@ -1319,6 +1319,7 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
             AuthenticationExecutionModel model = entityToModel(entity);
             executions.add(model);
         }
+        Collections.sort(executions, AuthenticationExecutionModel.ExecutionComparator.SINGLETON);
         return executions;
     }
 
                diff --git a/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java b/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
index 7d3760e..473bc4f 100755
--- a/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
+++ b/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
@@ -49,6 +49,7 @@ public class AuthenticationProcessor {
     public static enum Status {
         SUCCESS,
         CHALLENGE,
+        FORCE_CHALLENGE,
         FAILURE_CHALLENGE,
         FAILED,
         ATTEMPTED
@@ -216,6 +217,12 @@ public class AuthenticationProcessor {
 
         }
         @Override
+        public void forceChallenge(Response challenge) {
+            this.status = Status.FORCE_CHALLENGE;
+            this.challenge = challenge;
+
+        }
+        @Override
         public void failureChallenge(Error error, Response challenge) {
             this.error = error;
             this.status = Status.FAILURE_CHALLENGE;
@@ -437,7 +444,6 @@ public class AuthenticationProcessor {
         }
         List<AuthenticationExecutionModel> executions = realm.getAuthenticationExecutions(flowId);
         if (executions == null) return null;
-        Collections.sort(executions, AuthenticationExecutionModel.ExecutionComparator.SINGLETON);
         Response alternativeChallenge = null;
         AuthenticationExecutionModel challengedAlternativeExecution = null;
         boolean alternativeSuccessful = false;
@@ -513,6 +519,9 @@ public class AuthenticationProcessor {
                 clientSession.setAuthenticatorStatus(model.getId(), UserSessionModel.AuthenticatorStatus.FAILED);
                 if (context.challenge != null) return context.challenge;
                 throw new AuthException(context.error);
+            } else if (result == Status.FORCE_CHALLENGE) {
+                clientSession.setAuthenticatorStatus(model.getId(), UserSessionModel.AuthenticatorStatus.CHALLENGED);
+                return context.challenge;
             } else if (result == Status.CHALLENGE) {
                 logger.debugv("authenticator CHALLENGE: {0}", authenticatorModel.getProviderId());
                 if (model.isRequired() || (model.isOptional() && configuredFor)) {
                diff --git a/services/src/main/java/org/keycloak/authentication/AuthenticatorContext.java b/services/src/main/java/org/keycloak/authentication/AuthenticatorContext.java
index 1569c03..248eb7a 100755
--- a/services/src/main/java/org/keycloak/authentication/AuthenticatorContext.java
+++ b/services/src/main/java/org/keycloak/authentication/AuthenticatorContext.java
@@ -61,6 +61,9 @@ public interface AuthenticatorContext {
     void failure(AuthenticationProcessor.Error error);
     void failure(AuthenticationProcessor.Error error, Response response);
     void challenge(Response challenge);
+
+    void forceChallenge(Response challenge);
+
     void failureChallenge(AuthenticationProcessor.Error error, Response challenge);
     void attempted();
 }
                diff --git a/services/src/main/java/org/keycloak/authentication/AuthenticatorFactory.java b/services/src/main/java/org/keycloak/authentication/AuthenticatorFactory.java
index 04b445e..561e60d 100755
--- a/services/src/main/java/org/keycloak/authentication/AuthenticatorFactory.java
+++ b/services/src/main/java/org/keycloak/authentication/AuthenticatorFactory.java
@@ -1,9 +1,12 @@
 package org.keycloak.authentication;
 
+import org.keycloak.models.AuthenticationExecutionModel;
 import org.keycloak.models.AuthenticatorModel;
 import org.keycloak.provider.ConfiguredProvider;
 import org.keycloak.provider.ProviderFactory;
 
+import java.util.List;
+
 /**
 * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
 * @version $Revision: 1 $
@@ -13,4 +16,21 @@ public interface AuthenticatorFactory extends ProviderFactory<Authenticator>, Co
     String getDisplayCategory();
     String getDisplayType();
 
+    /**
+     * General authenticator type, i.e. totp, password, cert
+     *
+     * @return null if not a referencable type
+     */
+    String getReferenceType();
+
+    boolean isConfigurable();
+
+    /**
+     * What requirement settings are allowed.  For example, KERBEROS can only be required because of the way its challenges
+     * work.
+     *
+     * @return
+     */
+    AuthenticationExecutionModel.Requirement[] getRequirementChoices();
+
 }
                diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/CookieAuthenticatorFactory.java b/services/src/main/java/org/keycloak/authentication/authenticators/CookieAuthenticatorFactory.java
index 0f2ec07..525fb0b 100755
--- a/services/src/main/java/org/keycloak/authentication/authenticators/CookieAuthenticatorFactory.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/CookieAuthenticatorFactory.java
@@ -3,9 +3,11 @@ package org.keycloak.authentication.authenticators;
 import org.keycloak.Config;
 import org.keycloak.authentication.Authenticator;
 import org.keycloak.authentication.AuthenticatorFactory;
+import org.keycloak.models.AuthenticationExecutionModel;
 import org.keycloak.models.AuthenticatorModel;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.models.UserCredentialModel;
 import org.keycloak.provider.ProviderConfigProperty;
 
 import java.util.List;
@@ -48,6 +50,23 @@ public class CookieAuthenticatorFactory implements AuthenticatorFactory {
     }
 
     @Override
+    public String getReferenceType() {
+        return "cookie";
+    }
+
+    @Override
+    public boolean isConfigurable() {
+        return false;
+    }
+
+    public static final AuthenticationExecutionModel.Requirement[] REQUIREMENT_CHOICES = {AuthenticationExecutionModel.Requirement.ALTERNATIVE, AuthenticationExecutionModel.Requirement.DISABLED};
+
+    @Override
+    public AuthenticationExecutionModel.Requirement[] getRequirementChoices() {
+        return REQUIREMENT_CHOICES;
+    }
+
+    @Override
     public String getDisplayCategory() {
         return "Complete Authenticator";
     }
                diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/LoginFormOTPAuthenticatorFactory.java b/services/src/main/java/org/keycloak/authentication/authenticators/LoginFormOTPAuthenticatorFactory.java
index ef240cb..ff5043a 100755
--- a/services/src/main/java/org/keycloak/authentication/authenticators/LoginFormOTPAuthenticatorFactory.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/LoginFormOTPAuthenticatorFactory.java
@@ -3,9 +3,11 @@ package org.keycloak.authentication.authenticators;
 import org.keycloak.Config;
 import org.keycloak.authentication.Authenticator;
 import org.keycloak.authentication.AuthenticatorFactory;
+import org.keycloak.models.AuthenticationExecutionModel;
 import org.keycloak.models.AuthenticatorModel;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.models.UserCredentialModel;
 import org.keycloak.provider.ProviderConfigProperty;
 
 import java.util.List;
@@ -39,6 +41,27 @@ public class LoginFormOTPAuthenticatorFactory implements AuthenticatorFactory {
     }
 
     @Override
+    public String getReferenceType() {
+        return UserCredentialModel.TOTP;
+    }
+
+    @Override
+    public boolean isConfigurable() {
+        return false;
+    }
+
+    public static final AuthenticationExecutionModel.Requirement[] REQUIREMENT_CHOICES = {
+            AuthenticationExecutionModel.Requirement.REQUIRED,
+            AuthenticationExecutionModel.Requirement.OPTIONAL,
+            AuthenticationExecutionModel.Requirement.DISABLED};
+
+    @Override
+    public AuthenticationExecutionModel.Requirement[] getRequirementChoices() {
+        return REQUIREMENT_CHOICES;
+    }
+
+
+    @Override
     public void close() {
 
     }
                diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/LoginFormPasswordAuthenticatorFactory.java b/services/src/main/java/org/keycloak/authentication/authenticators/LoginFormPasswordAuthenticatorFactory.java
index 5da119e..e136c44 100755
--- a/services/src/main/java/org/keycloak/authentication/authenticators/LoginFormPasswordAuthenticatorFactory.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/LoginFormPasswordAuthenticatorFactory.java
@@ -3,9 +3,11 @@ package org.keycloak.authentication.authenticators;
 import org.keycloak.Config;
 import org.keycloak.authentication.Authenticator;
 import org.keycloak.authentication.AuthenticatorFactory;
+import org.keycloak.models.AuthenticationExecutionModel;
 import org.keycloak.models.AuthenticatorModel;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.models.UserCredentialModel;
 import org.keycloak.provider.ProviderConfigProperty;
 
 import java.util.List;
@@ -49,6 +51,24 @@ public class LoginFormPasswordAuthenticatorFactory implements AuthenticatorFacto
     }
 
     @Override
+    public String getReferenceType() {
+        return UserCredentialModel.PASSWORD;
+    }
+
+    @Override
+    public boolean isConfigurable() {
+        return true;
+    }
+    public static final AuthenticationExecutionModel.Requirement[] REQUIREMENT_CHOICES = {
+            AuthenticationExecutionModel.Requirement.REQUIRED,
+            AuthenticationExecutionModel.Requirement.DISABLED};
+
+    @Override
+    public AuthenticationExecutionModel.Requirement[] getRequirementChoices() {
+        return REQUIREMENT_CHOICES;
+    }
+
+    @Override
     public String getDisplayCategory() {
         return "Credential Validation";
     }
                diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/LoginFormUsernameAuthenticatorFactory.java b/services/src/main/java/org/keycloak/authentication/authenticators/LoginFormUsernameAuthenticatorFactory.java
index de86b08..0db3a77 100755
--- a/services/src/main/java/org/keycloak/authentication/authenticators/LoginFormUsernameAuthenticatorFactory.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/LoginFormUsernameAuthenticatorFactory.java
@@ -3,9 +3,11 @@ package org.keycloak.authentication.authenticators;
 import org.keycloak.Config;
 import org.keycloak.authentication.Authenticator;
 import org.keycloak.authentication.AuthenticatorFactory;
+import org.keycloak.models.AuthenticationExecutionModel;
 import org.keycloak.models.AuthenticatorModel;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.models.UserCredentialModel;
 import org.keycloak.provider.ProviderConfigProperty;
 
 import java.util.List;
@@ -49,6 +51,26 @@ public class LoginFormUsernameAuthenticatorFactory implements AuthenticatorFacto
     }
 
     @Override
+    public String getReferenceType() {
+        return null;
+    }
+
+    @Override
+    public boolean isConfigurable() {
+        return false;
+    }
+
+    public static final AuthenticationExecutionModel.Requirement[] REQUIREMENT_CHOICES = {
+            AuthenticationExecutionModel.Requirement.ALTERNATIVE,
+            AuthenticationExecutionModel.Requirement.DISABLED};
+
+    @Override
+    public AuthenticationExecutionModel.Requirement[] getRequirementChoices() {
+        return REQUIREMENT_CHOICES;
+    }
+
+
+    @Override
     public String getDisplayCategory() {
         return "User Validation";
     }
                diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/OTPFormAuthenticatorFactory.java b/services/src/main/java/org/keycloak/authentication/authenticators/OTPFormAuthenticatorFactory.java
index f5fe0e7..f197217 100755
--- a/services/src/main/java/org/keycloak/authentication/authenticators/OTPFormAuthenticatorFactory.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/OTPFormAuthenticatorFactory.java
@@ -3,9 +3,11 @@ package org.keycloak.authentication.authenticators;
 import org.keycloak.Config;
 import org.keycloak.authentication.Authenticator;
 import org.keycloak.authentication.AuthenticatorFactory;
+import org.keycloak.models.AuthenticationExecutionModel;
 import org.keycloak.models.AuthenticatorModel;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.models.UserCredentialModel;
 import org.keycloak.provider.ProviderConfigProperty;
 
 import java.util.List;
@@ -49,6 +51,25 @@ public class OTPFormAuthenticatorFactory implements AuthenticatorFactory {
     }
 
     @Override
+    public String getReferenceType() {
+        return UserCredentialModel.TOTP;
+    }
+
+    @Override
+    public boolean isConfigurable() {
+        return false;
+    }
+
+    public static final AuthenticationExecutionModel.Requirement[] REQUIREMENT_CHOICES = {
+            AuthenticationExecutionModel.Requirement.REQUIRED,
+            AuthenticationExecutionModel.Requirement.OPTIONAL,
+            AuthenticationExecutionModel.Requirement.DISABLED};
+
+    @Override
+    public AuthenticationExecutionModel.Requirement[] getRequirementChoices() {
+        return REQUIREMENT_CHOICES;
+    }
+    @Override
     public String getDisplayCategory() {
         return "Credential Validation";
     }
                diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/SpnegoAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/SpnegoAuthenticator.java
index 1045b55..3970fa8 100755
--- a/services/src/main/java/org/keycloak/authentication/authenticators/SpnegoAuthenticator.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/SpnegoAuthenticator.java
@@ -8,22 +8,31 @@ import org.keycloak.authentication.AuthenticatorContext;
 import org.keycloak.constants.KerberosConstants;
 import org.keycloak.events.Errors;
 import org.keycloak.login.LoginFormsProvider;
+import org.keycloak.models.ClientSessionModel;
 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 org.keycloak.services.managers.ClientSessionCode;
+import org.keycloak.services.messages.Messages;
 
 import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
+import java.net.URI;
+import java.util.HashMap;
 import java.util.Map;
 
+import static org.keycloak.util.HtmlUtils.escapeAttribute;
+
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
  * @version $Revision: 1 $
  */
 public class SpnegoAuthenticator extends AbstractFormAuthenticator implements Authenticator{
+    public static final String KERBEROS_DISABLED = "kerberos_disabled";
     protected static Logger logger = Logger.getLogger(SpnegoAuthenticator.class);
 
     @Override
@@ -41,7 +50,10 @@ public class SpnegoAuthenticator extends AbstractFormAuthenticator implements Au
     public void authenticate(AuthenticatorContext context) {
         HttpRequest request = context.getHttpRequest();
         String authHeader = request.getHttpHeaders().getRequestHeaders().getFirst(HttpHeaders.AUTHORIZATION);
-
+        if (isAction(context, KERBEROS_DISABLED)) {
+            context.attempted();
+            return;
+        }
         // Case when we don't yet have any Negotiate header
         if (authHeader == null) {
             if (isAlreadyChallenged(context)) {
@@ -49,7 +61,7 @@ public class SpnegoAuthenticator extends AbstractFormAuthenticator implements Au
                 return;
             }
             Response challenge = challengeNegotiation(context, null);
-            context.challenge(challenge);
+            context.forceChallenge(challenge);
             return;
         }
 
@@ -98,11 +110,68 @@ public class SpnegoAuthenticator extends AbstractFormAuthenticator implements Au
         if (logger.isTraceEnabled()) {
             logger.trace("Sending back " + HttpHeaders.WWW_AUTHENTICATE + ": " + negotiateHeader);
         }
-        LoginFormsProvider loginForm = loginForm(context);
+        if (context.getExecution().isRequired()) {
+            return context.getSession().getProvider(LoginFormsProvider.class)
+                    .setStatus(Response.Status.UNAUTHORIZED)
+                    .setResponseHeader(HttpHeaders.WWW_AUTHENTICATE, negotiateHeader)
+                    .setError(Messages.KERBEROS_NOT_ENABLED).createErrorPage();
+        } else {
+            return optionalChallengeRedirect(context, negotiateHeader);
+        }
+    }
+
+    // This is used for testing only.  Selenium will execute the HTML challenge sent back which results in the javascript
+    // redirecting.  Our old Selenium tests expect that the current URL will be the original openid redirect.
+    public static boolean bypassChallengeJavascript = false;
+
+    /**
+     * 401 challenge sent back that bypasses
+     * @param context
+     * @param negotiateHeader
+     * @return
+     */
+    protected Response optionalChallengeRedirect(AuthenticatorContext context, String negotiateHeader) {
+        ClientSessionCode code = new ClientSessionCode(context.getRealm(), context.getClientSession());
+        code.setAction(ClientSessionModel.Action.AUTHENTICATE.name());
+        URI action = getActionUrl(context, code, KERBEROS_DISABLED);
+
+        StringBuilder builder = new StringBuilder();
+
+        builder.append("<HTML>");
+        builder.append("<HEAD>");
+
+        builder.append("<TITLE>Kerberos Unsupported</TITLE>");
+        builder.append("</HEAD>");
+        if (bypassChallengeJavascript) {
+            builder.append("<BODY>");
+
+        } else {
+            builder.append("<BODY Onload=\"document.forms[0].submit()\">");
+        }
+        builder.append("<FORM METHOD=\"POST\" ACTION=\"" + action.toString() + "\">");
+        builder.append("<NOSCRIPT>");
+        builder.append("<P>JavaScript is disabled. We strongly recommend to enable it. You were unable to login via Kerberos.  Click the button below to login via an alternative method .</P>");
+        builder.append("<INPUT name=\"continue\" TYPE=\"SUBMIT\" VALUE=\"CONTINUE\" />");
+        builder.append("</NOSCRIPT>");
+
+        builder.append("</FORM></BODY></HTML>");
+        return Response.status(Response.Status.UNAUTHORIZED)
+                .header(HttpHeaders.WWW_AUTHENTICATE, negotiateHeader)
+                .type(MediaType.TEXT_HTML_TYPE)
+                .entity(builder.toString()).build();
+    }
 
-        loginForm.setStatus(Response.Status.UNAUTHORIZED);
-        loginForm.setResponseHeader(HttpHeaders.WWW_AUTHENTICATE, negotiateHeader);
-        return loginForm.createLogin();
+    protected Response formChallenge(AuthenticatorContext context, String negotiateHeader) {
+        ClientSessionCode code = new ClientSessionCode(context.getRealm(), context.getClientSession());
+        code.setAction(ClientSessionModel.Action.AUTHENTICATE.name());
+        URI action = getActionUrl(context, code, KERBEROS_DISABLED);
+        return context.getSession().getProvider(LoginFormsProvider.class)
+                .setClientSessionCode(new ClientSessionCode(context.getRealm(), context.getClientSession()).getCode())
+                .setActionUri(action)
+                .setStatus(Response.Status.UNAUTHORIZED)
+                .setResponseHeader(HttpHeaders.WWW_AUTHENTICATE, negotiateHeader)
+                .setUser(context.getUser())
+                .createForm("bypass_kerberos.ftl", new HashMap<String, Object>());
     }
 
 
                diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/SpnegoAuthenticatorFactory.java b/services/src/main/java/org/keycloak/authentication/authenticators/SpnegoAuthenticatorFactory.java
index a541305..21c56fc 100755
--- a/services/src/main/java/org/keycloak/authentication/authenticators/SpnegoAuthenticatorFactory.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/SpnegoAuthenticatorFactory.java
@@ -3,9 +3,11 @@ package org.keycloak.authentication.authenticators;
 import org.keycloak.Config;
 import org.keycloak.authentication.Authenticator;
 import org.keycloak.authentication.AuthenticatorFactory;
+import org.keycloak.models.AuthenticationExecutionModel;
 import org.keycloak.models.AuthenticatorModel;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.models.UserCredentialModel;
 import org.keycloak.provider.ProviderConfigProperty;
 
 import java.util.List;
@@ -44,6 +46,27 @@ public class SpnegoAuthenticatorFactory implements AuthenticatorFactory {
     }
 
     @Override
+    public String getReferenceType() {
+        return UserCredentialModel.KERBEROS;
+    }
+
+    @Override
+    public boolean isConfigurable() {
+        return false;
+    }
+
+    public static final AuthenticationExecutionModel.Requirement[] REQUIREMENT_CHOICES = {
+            AuthenticationExecutionModel.Requirement.ALTERNATIVE,
+            AuthenticationExecutionModel.Requirement.REQUIRED,
+            AuthenticationExecutionModel.Requirement.DISABLED};
+
+    @Override
+    public AuthenticationExecutionModel.Requirement[] getRequirementChoices() {
+        return REQUIREMENT_CHOICES;
+    }
+
+
+    @Override
     public String getId() {
         return PROVIDER_ID;
     }
                diff --git a/services/src/main/java/org/keycloak/authentication/AuthenticatorUtil.java b/services/src/main/java/org/keycloak/authentication/AuthenticatorUtil.java
index ca7dd4d..fcf34ba 100755
--- a/services/src/main/java/org/keycloak/authentication/AuthenticatorUtil.java
+++ b/services/src/main/java/org/keycloak/authentication/AuthenticatorUtil.java
@@ -10,12 +10,31 @@ import org.keycloak.models.RealmModel;
 import org.keycloak.models.utils.DefaultAuthenticationFlows;
 import org.keycloak.representations.idm.CredentialRepresentation;
 
+import java.util.LinkedList;
+import java.util.List;
+
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
  * @version $Revision: 1 $
  */
 public class AuthenticatorUtil {
 
+    public static List<AuthenticationExecutionModel> getEnabledExecutionsRecursively(RealmModel realm, String flowId) {
+        List<AuthenticationExecutionModel> executions = new LinkedList<>();
+        recurseExecutions(realm, flowId, executions);
+        return executions;
+
+    }
+
+    public static void recurseExecutions(RealmModel realm, String flowId, List<AuthenticationExecutionModel> executions) {
+        for (AuthenticationExecutionModel model : realm.getAuthenticationExecutions(flowId)) {
+            executions.add(model);
+            if (model.isAutheticatorFlow() && model.isEnabled()) {
+                recurseExecutions(realm, model.getAuthenticator(), executions);
+            }
+        }
+    }
+
     public static AuthenticationExecutionModel findExecutionByAuthenticator(RealmModel realm, String flowId, String authProviderId) {
         for (AuthenticationExecutionModel model : realm.getAuthenticationExecutions(flowId)) {
             if (model.isAutheticatorFlow()) {
                diff --git a/services/src/main/java/org/keycloak/services/messages/Messages.java b/services/src/main/java/org/keycloak/services/messages/Messages.java
index 8aaa06e..9f9070a 100755
--- a/services/src/main/java/org/keycloak/services/messages/Messages.java
+++ b/services/src/main/java/org/keycloak/services/messages/Messages.java
@@ -128,6 +128,8 @@ public class Messages {
 
     public static final String COULD_NOT_SEND_AUTHENTICATION_REQUEST = "couldNotSendAuthenticationRequestMessage";
 
+    public static final String KERBEROS_NOT_ENABLED="kerberosNotSetUp";
+
     public static final String UNEXPECTED_ERROR_HANDLING_REQUEST = "unexpectedErrorHandlingRequestMessage";
 
     public static final String INVALID_ACCESS_CODE = "invalidAccessCodeMessage";
                diff --git a/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationFlowResource.java b/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationFlowResource.java
new file mode 100755
index 0000000..7d2b719
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationFlowResource.java
@@ -0,0 +1,180 @@
+package org.keycloak.services.resources.admin;
+
+import org.jboss.logging.Logger;
+import org.jboss.resteasy.annotations.cache.NoCache;
+import org.jboss.resteasy.spi.NotFoundException;
+import org.keycloak.authentication.Authenticator;
+import org.keycloak.authentication.AuthenticatorFactory;
+import org.keycloak.authentication.AuthenticatorUtil;
+import org.keycloak.models.AuthenticationExecutionModel;
+import org.keycloak.models.AuthenticationFlowModel;
+import org.keycloak.models.AuthenticatorModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import java.util.LinkedList;
+import java.util.List;
+
+import static javax.ws.rs.core.Response.Status.NOT_FOUND;
+
+/**
+ * @author Pedro Igor
+ */
+public class AuthenticationFlowResource {
+
+    private final RealmModel realm;
+    private final KeycloakSession session;
+    private RealmAuth auth;
+    private AdminEventBuilder adminEvent;
+    private static Logger logger = Logger.getLogger(AuthenticationFlowResource.class);
+
+    public AuthenticationFlowResource(RealmModel realm, KeycloakSession session, RealmAuth auth, AdminEventBuilder adminEvent) {
+        this.realm = realm;
+        this.session = session;
+        this.auth = auth;
+        this.auth.init(RealmAuth.Resource.IDENTITY_PROVIDER);
+        this.adminEvent = adminEvent;
+    }
+
+    public static class AuthenticationExecutionRepresentation {
+        protected String execution;
+        protected String referenceType;
+        protected String requirement;
+        protected List<String> requirementChoices;
+        protected Boolean configurable;
+        protected Boolean subFlow;
+
+        public String getExecution() {
+            return execution;
+        }
+
+        public void setExecution(String execution) {
+            this.execution = execution;
+        }
+
+        public String getReferenceType() {
+            return referenceType;
+        }
+
+        public void setReferenceType(String referenceType) {
+            this.referenceType = referenceType;
+        }
+
+        public String getRequirement() {
+            return requirement;
+        }
+
+        public void setRequirement(String requirement) {
+            this.requirement = requirement;
+        }
+
+        public List<String> getRequirementChoices() {
+            return requirementChoices;
+        }
+
+        public void setRequirementChoices(List<String> requirementChoices) {
+            this.requirementChoices = requirementChoices;
+        }
+
+        public Boolean getConfigurable() {
+            return configurable;
+        }
+
+        public void setConfigurable(Boolean configurable) {
+            this.configurable = configurable;
+        }
+
+        public Boolean getSubFlow() {
+            return subFlow;
+        }
+
+        public void setSubFlow(Boolean subFlow) {
+            this.subFlow = subFlow;
+        }
+    }
+
+    @Path("/flow/{flowAlias}/executions")
+    @GET
+    @NoCache
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response getExecutions(@PathParam("flowAlias") String flowAlias) {
+        this.auth.requireView();
+
+        AuthenticationFlowModel flow = realm.getFlowByAlias(flowAlias);
+        if (flow == null) {
+            logger.debug("flow not found: " + flowAlias);
+            return Response.status(NOT_FOUND).build();
+        }
+        List<AuthenticationExecutionRepresentation> result = new LinkedList<>();
+        List<AuthenticationExecutionModel> executions = AuthenticatorUtil.getEnabledExecutionsRecursively(realm, flow.getId());
+        for (AuthenticationExecutionModel execution : executions) {
+            AuthenticationExecutionRepresentation rep = new AuthenticationExecutionRepresentation();
+            rep.setSubFlow(false);
+            rep.setRequirementChoices(new LinkedList<String>());
+            if (execution.isAutheticatorFlow()) {
+                AuthenticationFlowModel flowRef = realm.getAuthenticationFlowById(execution.getAuthenticator());
+                rep.setReferenceType(flowRef.getAlias());
+                rep.setExecution(execution.getId());
+                rep.getRequirementChoices().add(AuthenticationExecutionModel.Requirement.ALTERNATIVE.name());
+                rep.getRequirementChoices().add(AuthenticationExecutionModel.Requirement.REQUIRED.name());
+                rep.getRequirementChoices().add(AuthenticationExecutionModel.Requirement.DISABLED.name());
+                rep.setConfigurable(false);
+                rep.setExecution(execution.getId());
+                rep.setRequirement(execution.getRequirement().name());
+                result.add(rep);
+            } else {
+                if (!flow.getId().equals(execution.getParentFlow())) {
+                    rep.setSubFlow(true);
+                }
+                AuthenticatorModel authenticator = realm.getAuthenticatorById(execution.getAuthenticator());
+                AuthenticatorFactory factory = (AuthenticatorFactory)session.getKeycloakSessionFactory().getProviderFactory(Authenticator.class, authenticator.getProviderId());
+                if (factory.getReferenceType() == null) continue;
+                rep.setReferenceType(factory.getReferenceType());
+                rep.setConfigurable(factory.isConfigurable());
+                for (AuthenticationExecutionModel.Requirement choice : factory.getRequirementChoices()) {
+                    rep.getRequirementChoices().add(choice.name());
+                }
+                rep.setExecution(execution.getId());
+                rep.setRequirement(execution.getRequirement().name());
+                result.add(rep);
+
+            }
+
+        }
+        return Response.ok(result).build();
+    }
+
+    @Path("/flow/{flowAlias}/executions")
+    @PUT
+    @NoCache
+    @Consumes(MediaType.APPLICATION_JSON)
+    public void updateExecutions(@PathParam("flowAlias") String flowAlias, AuthenticationExecutionRepresentation rep) {
+        this.auth.requireManage();
+
+        AuthenticationFlowModel flow = realm.getFlowByAlias(flowAlias);
+        if (flow == null) {
+            logger.debug("flow not found: " + flowAlias);
+            throw new NotFoundException("flow not found");
+        }
+
+        AuthenticationExecutionModel model = realm.getAuthenticationExecutionById(rep.getExecution());
+        if (model == null) {
+            session.getTransaction().setRollbackOnly();
+            throw new NotFoundException("Illegal execution");
+
+        }
+        if (!model.getRequirement().name().equals(rep.getRequirement())) {
+            model.setRequirement(AuthenticationExecutionModel.Requirement.valueOf(rep.getRequirement()));
+            realm.updateAuthenticatorExecution(model);
+        }
+    }
+
+}
\ No newline at end of file
                diff --git a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java
index 2410331..f72da8c 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java
@@ -238,6 +238,15 @@ public class RealmAdminResource {
         return fed;
     }
 
+    @Path("authentication-flows")
+    public AuthenticationFlowResource flows() {
+        AuthenticationFlowResource resource = new AuthenticationFlowResource(realm, session, auth, adminEvent);
+        ResteasyProviderFactory.getInstance().injectProperties(resource);
+        //resourceContext.initResource(resource);
+        return resource;
+
+    }
+
     /**
      * Path for managing all realm-level or client-level roles defined in this realm by it's id.
      *
                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 cb6bdc7..eb41e5e 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
@@ -4,4 +4,3 @@ org.keycloak.authentication.authenticators.LoginFormPasswordAuthenticatorFactory
 org.keycloak.authentication.authenticators.LoginFormUsernameAuthenticatorFactory
 org.keycloak.authentication.authenticators.OTPFormAuthenticatorFactory
 org.keycloak.authentication.authenticators.SpnegoAuthenticatorFactory
-org.keycloak.authentication.authenticators.SetRequiredActionAuthenticatorFactory
\ No newline at end of file
                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 c5b25fd..b307baa 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
@@ -18,6 +18,7 @@ import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 import org.keycloak.adapters.HttpClientBuilder;
+import org.keycloak.authentication.authenticators.SpnegoAuthenticator;
 import org.keycloak.events.Details;
 import org.keycloak.federation.kerberos.CommonKerberosConfig;
 import org.keycloak.constants.KerberosConstants;
@@ -35,6 +36,7 @@ import org.keycloak.services.managers.RealmManager;
 import org.keycloak.testsuite.AssertEvents;
 import org.keycloak.testsuite.OAuthClient;
 import org.keycloak.testsuite.pages.AccountPasswordPage;
+import org.keycloak.testsuite.pages.BypassKerberosPage;
 import org.keycloak.testsuite.pages.LoginPage;
 import org.keycloak.testsuite.rule.KeycloakRule;
 import org.keycloak.testsuite.rule.WebResource;
@@ -60,6 +62,9 @@ public abstract class AbstractKerberosTest {
     protected LoginPage loginPage;
 
     @WebResource
+    protected BypassKerberosPage bypassPage;
+
+    @WebResource
     protected AccountPasswordPage changePasswordPage;
 
     protected abstract CommonKerberosConfig getKerberosConfig();
@@ -85,6 +90,7 @@ public abstract class AbstractKerberosTest {
     public void spnegoNotAvailableTest() throws Exception {
         initHttpClient(false);
 
+        SpnegoAuthenticator.bypassChallengeJavascript = true;
         driver.navigate().to(KERBEROS_APP_URL);
         String kcLoginPageLocation = driver.getCurrentUrl();
 
@@ -94,6 +100,7 @@ public abstract class AbstractKerberosTest {
         String responseText = response.readEntity(String.class);
         responseText.contains("Log in to test");
         response.close();
+        SpnegoAuthenticator.bypassChallengeJavascript = false;
     }
 
 
@@ -120,7 +127,7 @@ public abstract class AbstractKerberosTest {
 
         spnegoResponse.close();
         events.clear();
-    }
+     }
 
 
     @Test
@@ -133,6 +140,10 @@ public abstract class AbstractKerberosTest {
 
         // Login with username/password from kerberos
         changePasswordPage.open();
+        // Only needed if you are providing a click thru to bypass kerberos.  Currently there is a javascript
+        // to forward the user if kerberos isn't enabled.
+        //bypassPage.isCurrent();
+        //bypassPage.clickContinue();
         loginPage.assertCurrent();
         loginPage.login("jduke", "theduke");
         changePasswordPage.assertCurrent();
@@ -149,6 +160,10 @@ public abstract class AbstractKerberosTest {
         Assert.assertTrue(driver.getPageSource().contains("Your password has been updated."));
         changePasswordPage.logout();
 
+        // Only needed if you are providing a click thru to bypass kerberos.  Currently there is a javascript
+        // to forward the user if kerberos isn't enabled.
+        //bypassPage.isCurrent();
+        //bypassPage.clickContinue();
         // Login with old password doesn't work, but with new password works
         loginPage.login("jduke", "theduke");
         loginPage.assertCurrent();
@@ -156,7 +171,7 @@ public abstract class AbstractKerberosTest {
         changePasswordPage.assertCurrent();
         changePasswordPage.logout();
 
-        // Assert SPNEGO login still with the old password as mode is unsynced
+       // Assert SPNEGO login still with the old password as mode is unsynced
         events.clear();
         Response spnegoResponse = spnegoLogin("jduke", "theduke");
         Assert.assertEquals(302, spnegoResponse.getStatus());
@@ -221,12 +236,16 @@ public abstract class AbstractKerberosTest {
 
 
     protected Response spnegoLogin(String username, String password) {
+        SpnegoAuthenticator.bypassChallengeJavascript = true;
         driver.navigate().to(KERBEROS_APP_URL);
         String kcLoginPageLocation = driver.getCurrentUrl();
-
+        String location = "http://localhost:8081/auth/realms/test/protocol/openid-connect/auth?response_type=code&client_id=kerberos-app&redirect_uri=http%3A%2F%2Flocalhost%3A8081%2Fkerberos-portal&state=0%2F88a96ddd-84fe-4e77-8a46-02394d7b3a7d&login=true";
         // Request for SPNEGO login sent with Resteasy client
         spnegoSchemeFactory.setCredentials(username, password);
-        return client.target(kcLoginPageLocation).request().get();
+        Response response = client.target(kcLoginPageLocation).request().get();
+        SpnegoAuthenticator.bypassChallengeJavascript = false;
+        return response;
+
     }
 
 
                diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/KerberosLdapTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/KerberosLdapTest.java
index 765c88a..0192e35 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/KerberosLdapTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/KerberosLdapTest.java
@@ -44,7 +44,7 @@ public class KerberosLdapTest extends AbstractKerberosTest {
 
         @Override
         public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
-            CredentialHelper.setRequiredCredential(CredentialRepresentation.KERBEROS, appRealm);
+            CredentialHelper.setAlternativeCredential(CredentialRepresentation.KERBEROS, appRealm);
             URL url = getClass().getResource("/kerberos-test/kerberos-app-keycloak.json");
             keycloakRule.createApplicationDeployment()
                     .name("kerberos-portal").contextPath("/kerberos-portal")
@@ -109,6 +109,10 @@ public class KerberosLdapTest extends AbstractKerberosTest {
 
         // Login with username/password from kerberos
         changePasswordPage.open();
+        // Only needed if you are providing a click thru to bypass kerberos.  Currently there is a javascript
+        // to forward the user if kerberos isn't enabled.
+        //bypassPage.isCurrent();
+        //bypassPage.clickContinue();
         loginPage.assertCurrent();
         loginPage.login("jduke", "theduke");
         changePasswordPage.assertCurrent();
@@ -118,6 +122,11 @@ public class KerberosLdapTest extends AbstractKerberosTest {
         Assert.assertTrue(driver.getPageSource().contains("Your password has been updated."));
         changePasswordPage.logout();
 
+        // Only needed if you are providing a click thru to bypass kerberos.  Currently there is a javascript
+        // to forward the user if kerberos isn't enabled.
+        //bypassPage.isCurrent();
+        //bypassPage.clickContinue();
+
         // Login with old password doesn't work, but with new password works
         loginPage.login("jduke", "theduke");
         loginPage.assertCurrent();
@@ -139,6 +148,11 @@ public class KerberosLdapTest extends AbstractKerberosTest {
 
         // Change password back
         changePasswordPage.open();
+        // Only needed if you are providing a click thru to bypass kerberos.  Currently there is a javascript
+        // to forward the user if kerberos isn't enabled.
+        //bypassPage.isCurrent();
+        //bypassPage.clickContinue();
+
         loginPage.login("jduke", "newPass");
         changePasswordPage.assertCurrent();
         changePasswordPage.changePassword("newPass", "theduke", "theduke");
                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
index cc0f80e..edb5afa 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/KerberosStandaloneTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/KerberosStandaloneTest.java
@@ -45,7 +45,7 @@ public class KerberosStandaloneTest extends AbstractKerberosTest {
         @Override
         public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
 
-            CredentialHelper.setRequiredCredential(CredentialRepresentation.KERBEROS, appRealm);
+            CredentialHelper.setAlternativeCredential(CredentialRepresentation.KERBEROS, appRealm);
             URL url = getClass().getResource("/kerberos-test/kerberos-app-keycloak.json");
             keycloakRule.createApplicationDeployment()
                     .name("kerberos-portal").contextPath("/kerberos-portal")
@@ -104,6 +104,11 @@ public class KerberosStandaloneTest extends AbstractKerberosTest {
         assertUser("hnelson", "hnelson@keycloak.org", null, null, false);
     }
 
+    @Test
+    @Override
+    public void usernamePasswordLoginTest() throws Exception {
+        super.usernamePasswordLoginTest();
+    }
 
     @Test
     public void updateProfileEnabledTest() throws Exception {
                diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/BypassKerberosPage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/BypassKerberosPage.java
new file mode 100755
index 0000000..10554d3
--- /dev/null
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/BypassKerberosPage.java
@@ -0,0 +1,50 @@
+/*
+ * 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.pages;
+
+import org.keycloak.testsuite.OAuthClient;
+import org.keycloak.testsuite.rule.WebResource;
+import org.openqa.selenium.By;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.support.FindBy;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class BypassKerberosPage extends AbstractPage {
+
+    @FindBy(name = "continue")
+    private WebElement continueButton;
+
+   public boolean isCurrent() {
+        return driver.getTitle().equals("Log in to test") || driver.getTitle().equals("Anmeldung bei test");
+    }
+
+    public void clickContinue() {
+        continueButton.click();
+    }
+
+    @Override
+    public void open() throws Exception {
+
+    }
+}
                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
index a88d8b1..8ac3a15 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/utils/CredentialHelper.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/utils/CredentialHelper.java
@@ -1,9 +1,7 @@
 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;
@@ -19,18 +17,28 @@ import org.keycloak.representations.idm.CredentialRepresentation;
 public class CredentialHelper {
 
     public static void setRequiredCredential(String type, RealmModel realm) {
+        AuthenticationExecutionModel.Requirement requirement = AuthenticationExecutionModel.Requirement.REQUIRED;
+        setCredentialRequirement(type, realm, requirement);
+    }
+
+    public static void setAlternativeCredential(String type, RealmModel realm) {
+        AuthenticationExecutionModel.Requirement requirement = AuthenticationExecutionModel.Requirement.ALTERNATIVE;
+        setCredentialRequirement(type, realm, requirement);
+    }
+
+    public static void setCredentialRequirement(String type, RealmModel realm, AuthenticationExecutionModel.Requirement requirement) {
         if (type.equals(CredentialRepresentation.TOTP)) {
             String providerId = OTPFormAuthenticatorFactory.PROVIDER_ID;
             String flowAlias = DefaultAuthenticationFlows.FORMS_FLOW;
-            requireAuthentication(realm, providerId, flowAlias);
+            authenticationRequirement(realm, providerId, flowAlias, requirement);
         } else if (type.equals(CredentialRepresentation.KERBEROS)) {
             String providerId = SpnegoAuthenticatorFactory.PROVIDER_ID;
             String flowAlias = DefaultAuthenticationFlows.BROWSER_FLOW;
-            alternativeAuthentication(realm, providerId, flowAlias);
+            authenticationRequirement(realm, providerId, flowAlias, requirement);
         } else if (type.equals(CredentialRepresentation.PASSWORD)) {
             String providerId = LoginFormPasswordAuthenticatorFactory.PROVIDER_ID;
             String flowAlias = DefaultAuthenticationFlows.FORMS_FLOW;
-            requireAuthentication(realm, providerId, flowAlias);
+            authenticationRequirement(realm, providerId, flowAlias, requirement);
         }
     }
 
@@ -42,11 +50,6 @@ public class CredentialHelper {
 
     }
 
-    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);