keycloak-aplcache

terms and conditions

6/11/2015 3:39:08 PM

Changes

Details

diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/users.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/users.js
index 92e5db2..a2573cb 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/users.js
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/users.js
@@ -195,7 +195,7 @@ module.controller('UserListCtrl', function($scope, realm, User) {
 
 
 
-module.controller('UserDetailCtrl', function($scope, realm, user, User, UserFederationInstances, $location, Dialog, Notifications) {
+module.controller('UserDetailCtrl', function($scope, realm, user, User, UserFederationInstances, RequiredActions, $location, Dialog, Notifications) {
     $scope.realm = realm;
     $scope.create = !user.id;
     $scope.editUsername = $scope.create || $scope.realm.editUsernameAllowed;
@@ -219,14 +219,29 @@ module.controller('UserDetailCtrl', function($scope, realm, user, User, UserFede
     }
 
     $scope.changed = false; // $scope.create;
-
+    if (user.requiredActions) {
+        for (var i = 0; i < user.requiredActions.length; i++) {
+            console.log("user require action: " + user.requiredActions[i]);
+        }
+    }
     // ID - Name map for required actions. IDs are enum names.
-    $scope.userReqActionList = [
+    RequiredActions.query({id: realm.realm}, function(data) {
+        $scope.userReqActionList = [];
+        for (var i = 0; i < data.length; i++) {
+            console.log("listed required action: " + data[i].text);
+            item = { id: data[i].id, text: data[i].text };
+            $scope.userReqActionList.push(item);
+        }
+
+    });
+
+        /*[
         {id: "VERIFY_EMAIL", text: "Verify Email"},
         {id: "UPDATE_PROFILE", text: "Update Profile"},
         {id: "CONFIGURE_TOTP", text: "Configure Totp"},
         {id: "UPDATE_PASSWORD", text: "Update Password"}
     ];
+    */
 
     $scope.$watch('user', function() {
         if (!angular.equals($scope.user, user)) {
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 763d57f..e9f877b 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
@@ -186,6 +186,12 @@ module.factory('RealmAdminEvents', function($resource) {
     });
 });
 
+module.factory('RequiredActions', function($resource) {
+    return $resource(authUrl + '/admin/realms/:id/required-actions', {
+        id : '@realm'
+    });
+});
+
 module.factory('RealmLDAPConnectionTester', function($resource) {
     return $resource(authUrl + '/admin/realms/:realm/testLDAPConnection');
 });
diff --git a/forms/common-themes/src/main/resources/theme/base/login/login.ftl b/forms/common-themes/src/main/resources/theme/base/login/login.ftl
index e3a3456..1f8cd86 100755
--- a/forms/common-themes/src/main/resources/theme/base/login/login.ftl
+++ b/forms/common-themes/src/main/resources/theme/base/login/login.ftl
@@ -1,76 +1,76 @@
-<#import "template.ftl" as layout>
-<@layout.registrationLayout displayInfo=social.displayInfo; section>
-    <#if section = "title">
-        ${msg("loginTitle",(realm.name!''))}
-    <#elseif section = "header">
-        ${msg("loginTitleHtml",(realm.name!''))}
-    <#elseif section = "form">
-        <#if realm.password>
-            <form id="kc-form-login" class="${properties.kcFormClass!}" action="${url.loginAction}" method="post">
-                <div class="${properties.kcFormGroupClass!}">
-                    <div class="${properties.kcLabelWrapperClass!}">
-                        <label for="username" class="${properties.kcLabelClass!}"><#if !realm.registrationEmailAsUsername>${msg("usernameOrEmail")}<#else>${msg("email")}</#if></label>
-                    </div>
-
-                    <div class="${properties.kcInputWrapperClass!}">
-                        <input id="username" class="${properties.kcInputClass!}" name="username" value="${(login.username!'')?html}" type="text" autofocus />
-                    </div>
-                </div>
-
-                <div class="${properties.kcFormGroupClass!}">
-                    <div class="${properties.kcLabelWrapperClass!}">
-                        <label for="password" class="${properties.kcLabelClass!}">${msg("password")}</label>
-                    </div>
-
-                    <div class="${properties.kcInputWrapperClass!}">
-                        <input id="password" class="${properties.kcInputClass!}" name="password" type="password" />
-                    </div>
-                </div>
-
-                <div class="${properties.kcFormGroupClass!}">
-                    <div id="kc-form-options" class="${properties.kcFormOptionsClass!}">
-                        <#if realm.rememberMe>
-                            <div class="checkbox">
-                                <label>
-                                    <#if login.rememberMe??>
-                                        <input id="rememberMe" name="rememberMe" type="checkbox" tabindex="3" checked> ${msg("rememberMe")}
-                                    <#else>
-                                        <input id="rememberMe" name="rememberMe" type="checkbox" tabindex="3"> ${msg("rememberMe")}
-                                    </#if>
-                                </label>
-                            </div>
-                        </#if>
-                        <div class="${properties.kcFormOptionsWrapperClass!}">
-                            <#if realm.resetPasswordAllowed>
-                                <span><a href="${url.loginPasswordResetUrl}">${msg("doForgotPassword")}</a></span>
-                            </#if>
-                        </div>
-                    </div>
-
-                    <div id="kc-form-buttons" class="${properties.kcFormButtonsClass!}">
-                        <div class="${properties.kcFormButtonsWrapperClass!}">
-                            <input class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonLargeClass!}" name="login" id="kc-login" type="submit" value="${msg("doLogIn")}"/>
-                            <input class="${properties.kcButtonClass!} ${properties.kcButtonDefaultClass!} ${properties.kcButtonLargeClass!}" name="cancel" id="kc-cancel" type="submit" value="${msg("doCancel")}"/>
-                        </div>
-                     </div>
-                </div>
-            </form>
-        </#if>
-    <#elseif section = "info" >
-        <#if realm.password && realm.registrationAllowed>
-            <div id="kc-registration">
-                <span>${msg("noAccount")} <a href="${url.registrationUrl}">${msg("doRegister")}</a></span>
-            </div>
-        </#if>
-
-        <#if realm.password && social.providers??>
-            <div id="kc-social-providers">
-                <ul>
-                    <#list social.providers as p>
-                        <li><a href="${p.loginUrl}" id="zocial-${p.alias}" class="zocial ${p.providerId}"> <span class="text">${p.alias}</span></a></li>
-                    </#list>
-                </ul>
-            </div>
-        </#if>
-    </#if>
-</@layout.registrationLayout>
+<#import "template.ftl" as layout>
+<@layout.registrationLayout displayInfo=social.displayInfo; section>
+    <#if section = "title">
+        ${msg("loginTitle",(realm.name!''))}
+    <#elseif section = "header">
+        ${msg("loginTitleHtml",(realm.name!''))}
+    <#elseif section = "form">
+        <#if realm.password>
+            <form id="kc-form-login" class="${properties.kcFormClass!}" action="${url.loginAction}" method="post">
+                <div class="${properties.kcFormGroupClass!}">
+                    <div class="${properties.kcLabelWrapperClass!}">
+                        <label for="username" class="${properties.kcLabelClass!}"><#if !realm.registrationEmailAsUsername>${msg("usernameOrEmail")}<#else>${msg("email")}</#if></label>
+                    </div>
+
+                    <div class="${properties.kcInputWrapperClass!}">
+                        <input id="username" class="${properties.kcInputClass!}" name="username" value="${(login.username!'')?html}" type="text" autofocus />
+                    </div>
+                </div>
+
+                <div class="${properties.kcFormGroupClass!}">
+                    <div class="${properties.kcLabelWrapperClass!}">
+                        <label for="password" class="${properties.kcLabelClass!}">${msg("password")}</label>
+                    </div>
+
+                    <div class="${properties.kcInputWrapperClass!}">
+                        <input id="password" class="${properties.kcInputClass!}" name="password" type="password" />
+                    </div>
+                </div>
+
+                <div class="${properties.kcFormGroupClass!}">
+                    <div id="kc-form-options" class="${properties.kcFormOptionsClass!}">
+                        <#if realm.rememberMe>
+                            <div class="checkbox">
+                                <label>
+                                    <#if login.rememberMe??>
+                                        <input id="rememberMe" name="rememberMe" type="checkbox" tabindex="3" checked> ${msg("rememberMe")}
+                                    <#else>
+                                        <input id="rememberMe" name="rememberMe" type="checkbox" tabindex="3"> ${msg("rememberMe")}
+                                    </#if>
+                                </label>
+                            </div>
+                        </#if>
+                        <div class="${properties.kcFormOptionsWrapperClass!}">
+                            <#if realm.resetPasswordAllowed>
+                                <span><a href="${url.loginPasswordResetUrl}">${msg("doForgotPassword")}</a></span>
+                            </#if>
+                        </div>
+                    </div>
+
+                    <div id="kc-form-buttons" class="${properties.kcFormButtonsClass!}">
+                        <div class="${properties.kcFormButtonsWrapperClass!}">
+                            <input class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonLargeClass!}" name="login" id="kc-login" type="submit" value="${msg("doLogIn")}"/>
+                            <input class="${properties.kcButtonClass!} ${properties.kcButtonDefaultClass!} ${properties.kcButtonLargeClass!}" name="cancel" id="kc-cancel" type="submit" value="${msg("doCancel")}"/>
+                        </div>
+                     </div>
+                </div>
+            </form>
+        </#if>
+    <#elseif section = "info" >
+        <#if realm.password && realm.registrationAllowed>
+            <div id="kc-registration">
+                <span>${msg("noAccount")} <a href="${url.registrationUrl}">${msg("doRegister")}</a></span>
+            </div>
+        </#if>
+
+        <#if realm.password && social.providers??>
+            <div id="kc-social-providers">
+                <ul>
+                    <#list social.providers as p>
+                        <li><a href="${p.loginUrl}" id="zocial-${p.alias}" class="zocial ${p.providerId}"> <span class="text">${p.alias}</span></a></li>
+                    </#list>
+                </ul>
+            </div>
+        </#if>
+    </#if>
+</@layout.registrationLayout>
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 d99be80..347ad36 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
@@ -4,6 +4,8 @@ doCancel=Abbrechen
 doSubmit=Absenden
 doYes=Ja
 doNo=Nein
+doAccept=Accept
+doDecline=Decline
 doForgotPassword=Passwort vergessen?
 doClickHere=hier klicken
 
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 9ee2d07..8aac702 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,6 +4,8 @@ doCancel=Cancel
 doSubmit=Submit
 doYes=Yes
 doNo=No
+doAccept=Accept
+doDecline=Decline
 doForgotPassword=Forgot Password?
 doClickHere=Click here
 
@@ -22,6 +24,8 @@ emailForgotTitle=Forgot Your Password?
 updatePasswordTitle=Update password
 codeSuccessTitle=Success code
 codeErrorTitle=Error code\: {0}
+termsTitle=Terms and Conditions
+termsTitleHtml=Terms and Conditions
 
 noAccount=New user?
 username=Username
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 8f7be07..0fb25d7 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
@@ -4,6 +4,8 @@ doCancel=Annulla
 doSubmit=Invia
 doYes=Si
 doNo=No
+doAccept=Accept
+doDecline=Decline
 doForgotPassword=Password Dimenticata?
 doClickHere=Clicca qui
 
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 4ed6f4d..a828f72 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
@@ -3,6 +3,8 @@ doRegister=Cadastre-se
 doCancel=Cancelar
 doSubmit=Ok
 doYes=Sim
+doAccept=Accept
+doDecline=Decline
 doNo=N\u00E3o
 doForgotPassword=Esqueceu sua senha?
 doClickHere=Clique aqui
diff --git a/forms/common-themes/src/main/resources/theme/base/login/terms.ftl b/forms/common-themes/src/main/resources/theme/base/login/terms.ftl
new file mode 100755
index 0000000..2948b0e
--- /dev/null
+++ b/forms/common-themes/src/main/resources/theme/base/login/terms.ftl
@@ -0,0 +1,29 @@
+<#import "template.ftl" as layout>
+<@layout.registrationLayout displayMessage=false; section>
+    <#if section = "title">
+    ${msg("termsTitle")}
+    <#elseif section = "header">
+    ${msg("termsTitleHtml")}
+    <#elseif section = "form">
+    <div id="kc-info-message">
+        <textarea class="${properties.kcTextareaClass!}" rows="20" cols="120">
+        Apache License
+        Version 2.0, January 2004
+        http://www.apache.org/licenses/
+
+        TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+        </textarea>
+        <form class="form-actions" action="${requiredActionUrl("terms_and_conditions", "")}" 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="accept" id="kc-login" type="submit" value="${msg("doAccept")}"/>
+                        <input class="${properties.kcButtonClass!} ${properties.kcButtonDefaultClass!} ${properties.kcButtonLargeClass!}" name="cancel" id="kc-cancel" type="submit" value="${msg("doDecline")}"/>
+                    </div>
+                </div>
+            </div>
+        </form>
+    </div>
+    </#if>
+</@layout.registrationLayout>
\ No newline at end of file
diff --git a/forms/login-api/src/main/java/org/keycloak/login/LoginFormsProvider.java b/forms/login-api/src/main/java/org/keycloak/login/LoginFormsProvider.java
index adfd3f8..de582de 100755
--- a/forms/login-api/src/main/java/org/keycloak/login/LoginFormsProvider.java
+++ b/forms/login-api/src/main/java/org/keycloak/login/LoginFormsProvider.java
@@ -2,6 +2,7 @@ package org.keycloak.login;
 
 import java.net.URI;
 import java.util.List;
+import java.util.Map;
 
 import javax.ws.rs.core.HttpHeaders;
 import javax.ws.rs.core.MultivaluedMap;
@@ -24,6 +25,8 @@ public interface LoginFormsProvider extends Provider {
 
     public Response createResponse(UserModel.RequiredAction action);
 
+    Response createForm(String form, Map<String, Object> attributes);
+
     public Response createLogin();
 
     public Response createPasswordReset();
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 3c0f56f..1f59e84 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
@@ -27,6 +27,7 @@ import org.keycloak.login.freemarker.model.OAuthGrantBean;
 import org.keycloak.login.freemarker.model.ProfileBean;
 import org.keycloak.login.freemarker.model.RealmBean;
 import org.keycloak.login.freemarker.model.RegisterBean;
+import org.keycloak.login.freemarker.model.RequiredActionUrlFormatterMethod;
 import org.keycloak.login.freemarker.model.TotpBean;
 import org.keycloak.login.freemarker.model.UrlBean;
 import org.keycloak.models.ClientModel;
@@ -205,6 +206,7 @@ import java.util.concurrent.TimeUnit;
             uriBuilder.replaceQuery(null);
         }
         URI baseUri = uriBuilder.build();
+        attributes.put("requiredActionUrl", new RequiredActionUrlFormatterMethod(realm, baseUri));
 
         if (realm != null) {
             attributes.put("realm", new RealmBean(realm));
@@ -272,6 +274,98 @@ import java.util.concurrent.TimeUnit;
         }
     }
 
+    @Override
+    public Response createForm(String form, Map<String, Object> attributes) {
+        RealmModel realm = session.getContext().getRealm();
+        ClientModel client = session.getContext().getClient();
+        UriInfo uriInfo = session.getContext().getUri();
+
+        MultivaluedMap<String, String> queryParameterMap = queryParams != null ? queryParams : new MultivaluedMapImpl<String, String>();
+
+        String requestURI = uriInfo.getBaseUri().getPath();
+        UriBuilder uriBuilder = UriBuilder.fromUri(requestURI);
+
+        for (String k : queryParameterMap.keySet()) {
+
+            Object[] objects = queryParameterMap.get(k).toArray();
+            if (objects.length == 1 && objects[0] == null) continue; //
+            uriBuilder.replaceQueryParam(k, objects);
+        }
+        if (accessCode != null) {
+            uriBuilder.replaceQueryParam(OAuth2Constants.CODE, accessCode);
+        }
+        URI baseUri = uriBuilder.build();
+
+        ThemeProvider themeProvider = session.getProvider(ThemeProvider.class, "extending");
+        Theme theme;
+        try {
+            theme = themeProvider.getTheme(realm.getLoginTheme(), Theme.Type.LOGIN);
+        } catch (IOException e) {
+            logger.error("Failed to create theme", e);
+            return Response.serverError().build();
+        }
+
+        try {
+            attributes.put("properties", theme.getProperties());
+        } catch (IOException e) {
+            logger.warn("Failed to load properties", e);
+        }
+
+        Properties messagesBundle;
+        Locale locale = LocaleHelper.getLocale(realm, user, uriInfo, session.getContext().getRequestHeaders());
+        try {
+            messagesBundle = theme.getMessages(locale);
+            attributes.put("msg", new MessageFormatterMethod(locale, messagesBundle));
+        } catch (IOException e) {
+            logger.warn("Failed to load messages", e);
+            messagesBundle = new Properties();
+        }
+
+        MessagesPerFieldBean messagesPerField = new MessagesPerFieldBean();
+        if (messages != null) {
+            MessageBean wholeMessage = new MessageBean(null, messageType);
+            for (FormMessage message : this.messages) {
+                String formattedMessageText = formatMessage(message, messagesBundle, locale);
+                if (formattedMessageText != null) {
+                    wholeMessage.appendSummaryLine(formattedMessageText);
+                    messagesPerField.addMessage(message.getField(), formattedMessageText, messageType);
+                }
+            }
+            attributes.put("message", wholeMessage);
+        }
+        attributes.put("messagesPerField", messagesPerField);
+
+        if (status == null) {
+            status = Response.Status.OK;
+        }
+
+        if (realm != null) {
+            attributes.put("realm", new RealmBean(realm));
+            attributes.put("social", new IdentityProviderBean(realm, baseUri, uriInfo));
+            attributes.put("url", new UrlBean(realm, theme, baseUri, this.actionUri));
+            attributes.put("requiredActionUrl", new RequiredActionUrlFormatterMethod(realm, baseUri));
+
+            if (realm.isInternationalizationEnabled()) {
+                UriBuilder b = UriBuilder.fromUri(baseUri).path(uriInfo.getPath());
+                attributes.put("locale", new LocaleBean(realm, locale, b, messagesBundle));
+            }
+        }
+        try {
+            String result = freeMarker.processTemplate(attributes, form, theme);
+            Response.ResponseBuilder builder = Response.status(status).type(MediaType.TEXT_HTML).entity(result);
+            BrowserSecurityHeaderSetup.headers(builder, realm);
+            for (Map.Entry<String, String> entry : httpResponseHeaders.entrySet()) {
+                builder.header(entry.getKey(), entry.getValue());
+            }
+            LocaleHelper.updateLocaleCookie(builder, locale, realm, uriInfo, Urls.localeCookiePath(baseUri, realm.getName()));
+            return builder.build();
+        } catch (FreeMarkerException e) {
+            logger.error("Failed to process template", e);
+            return Response.serverError().build();
+        }
+    }
+
+
     public Response createLogin() {
         return createResponse(LoginFormsPages.LOGIN);
     }
diff --git a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/RequiredActionUrlFormatterMethod.java b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/RequiredActionUrlFormatterMethod.java
new file mode 100755
index 0000000..3056e26
--- /dev/null
+++ b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/RequiredActionUrlFormatterMethod.java
@@ -0,0 +1,32 @@
+package org.keycloak.login.freemarker.model;
+
+import freemarker.template.TemplateMethodModelEx;
+import freemarker.template.TemplateModelException;
+import org.keycloak.models.RealmModel;
+import org.keycloak.services.Urls;
+
+import java.net.URI;
+import java.text.MessageFormat;
+import java.util.List;
+import java.util.Locale;
+import java.util.Properties;
+
+/**
+ */
+public class RequiredActionUrlFormatterMethod implements TemplateMethodModelEx {
+    private final String realm;
+    private final URI baseUri;
+
+    public RequiredActionUrlFormatterMethod(RealmModel realm, URI baseUri) {
+        this.realm = realm.getName();
+        this.baseUri = baseUri;
+    }
+
+    @Override
+    public Object exec(List list) throws TemplateModelException {
+        String action = list.get(0).toString();
+        String relativePath = list.get(1).toString();
+        String url = Urls.requiredActionBase(baseUri).path(relativePath).build(realm, action).toString();
+        return url;
+    }
+}
diff --git a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/UrlBean.java b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/UrlBean.java
index b4b3f16..c652002 100755
--- a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/UrlBean.java
+++ b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/UrlBean.java
@@ -1,102 +1,102 @@
-/*
- * 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.login.freemarker.model;
-
-import org.keycloak.freemarker.Theme;
-import org.keycloak.models.RealmModel;
-import org.keycloak.services.Urls;
-
-import java.net.URI;
-
-/**
- * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
- */
-public class UrlBean {
-
-    private final URI actionuri;
-    private URI baseURI;
-    private Theme theme;
-    private String realm;
-
-    public UrlBean(RealmModel realm, Theme theme, URI baseURI, URI actionUri) {
-        this.realm = realm.getName();
-        this.theme = theme;
-        this.baseURI = baseURI;
-        this.actionuri = actionUri;
-    }
-
-    public String getLoginAction() {
-        if (this.actionuri != null) {
-            return this.actionuri.toString();
-        }
-        return Urls.realmLoginAction(baseURI, realm).toString();
-    }
-
-    public String getLoginUrl() {
-        return Urls.realmLoginPage(baseURI, realm).toString();
-    }
-
-    public String getRegistrationAction() {
-        return Urls.realmRegisterAction(baseURI, realm).toString();
-    }
-
-    public String getRegistrationUrl() {
-        return Urls.realmRegisterPage(baseURI, realm).toString();
-    }
-
-    public String getLoginUpdatePasswordUrl() {
-        return Urls.loginActionUpdatePassword(baseURI, realm).toString();
-    }
-
-    public String getLoginUpdateTotpUrl() {
-        return Urls.loginActionUpdateTotp(baseURI, realm).toString();
-    }
-
-    public String getLoginUpdateProfileUrl() {
-        return Urls.loginActionUpdateProfile(baseURI, realm).toString();
-    }
-
-    public String getLoginPasswordResetUrl() {
-        return Urls.loginPasswordReset(baseURI, realm).toString();
-    }
-
-    public String getLoginUsernameReminderUrl() {
-        return Urls.loginUsernameReminder(baseURI, realm).toString();
-    }
-
-    public String getLoginEmailVerificationUrl() {
-        return Urls.loginActionEmailVerification(baseURI, realm).toString();
-    }
-
-    public String getOauthAction() {
-        if (this.actionuri != null) {
-            return this.actionuri.getPath();
-        }
-
-        return Urls.realmOauthAction(baseURI, realm).toString();
-    }
-
-    public String getResourcesPath() {
-        URI uri = Urls.themeRoot(baseURI);
-        return uri.getPath() + "/" + theme.getType().toString().toLowerCase() +"/" + theme.getName();
-    }
-}
+/*
+ * 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.login.freemarker.model;
+
+import org.keycloak.freemarker.Theme;
+import org.keycloak.models.RealmModel;
+import org.keycloak.services.Urls;
+
+import java.net.URI;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class UrlBean {
+
+    private final URI actionuri;
+    private URI baseURI;
+    private Theme theme;
+    private String realm;
+
+    public UrlBean(RealmModel realm, Theme theme, URI baseURI, URI actionUri) {
+        this.realm = realm.getName();
+        this.theme = theme;
+        this.baseURI = baseURI;
+        this.actionuri = actionUri;
+    }
+
+    public String getLoginAction() {
+        if (this.actionuri != null) {
+            return this.actionuri.toString();
+        }
+        return Urls.realmLoginAction(baseURI, realm).toString();
+    }
+
+    public String getLoginUrl() {
+        return Urls.realmLoginPage(baseURI, realm).toString();
+    }
+
+    public String getRegistrationAction() {
+        return Urls.realmRegisterAction(baseURI, realm).toString();
+    }
+
+    public String getRegistrationUrl() {
+        return Urls.realmRegisterPage(baseURI, realm).toString();
+    }
+
+    public String getLoginUpdatePasswordUrl() {
+        return Urls.loginActionUpdatePassword(baseURI, realm).toString();
+    }
+
+    public String getLoginUpdateTotpUrl() {
+        return Urls.loginActionUpdateTotp(baseURI, realm).toString();
+    }
+
+    public String getLoginUpdateProfileUrl() {
+        return Urls.loginActionUpdateProfile(baseURI, realm).toString();
+    }
+
+    public String getLoginPasswordResetUrl() {
+        return Urls.loginPasswordReset(baseURI, realm).toString();
+    }
+
+    public String getLoginUsernameReminderUrl() {
+        return Urls.loginUsernameReminder(baseURI, realm).toString();
+    }
+
+    public String getLoginEmailVerificationUrl() {
+        return Urls.loginActionEmailVerification(baseURI, realm).toString();
+    }
+
+    public String getOauthAction() {
+        if (this.actionuri != null) {
+            return this.actionuri.getPath();
+        }
+
+        return Urls.realmOauthAction(baseURI, realm).toString();
+    }
+
+    public String getResourcesPath() {
+        URI uri = Urls.themeRoot(baseURI);
+        return uri.getPath() + "/" + theme.getType().toString().toLowerCase() +"/" + theme.getName();
+    }
+}
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 2adcb25..467040f 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
@@ -59,7 +59,8 @@ public class ModelToRepresentation {
         rep.setFederationLink(user.getFederationLink());
 
         List<String> reqActions = new ArrayList<String>();
-        for (String ra : user.getRequiredActions()){
+        Set<String> requiredActions = user.getRequiredActions();
+        for (String ra : requiredActions){
             reqActions.add(ra);
         }
 
diff --git a/services/src/main/java/org/keycloak/authentication/actions/TermsAndConditions.java b/services/src/main/java/org/keycloak/authentication/actions/TermsAndConditions.java
new file mode 100755
index 0000000..5daafa8
--- /dev/null
+++ b/services/src/main/java/org/keycloak/authentication/actions/TermsAndConditions.java
@@ -0,0 +1,115 @@
+package org.keycloak.authentication.actions;
+
+import org.keycloak.Config;
+import org.keycloak.Version;
+import org.keycloak.authentication.RequiredActionContext;
+import org.keycloak.authentication.RequiredActionFactory;
+import org.keycloak.authentication.RequiredActionProvider;
+import org.keycloak.events.Errors;
+import org.keycloak.freemarker.BrowserSecurityHeaderSetup;
+import org.keycloak.freemarker.FreeMarkerException;
+import org.keycloak.freemarker.FreeMarkerUtil;
+import org.keycloak.freemarker.Theme;
+import org.keycloak.freemarker.ThemeProvider;
+import org.keycloak.login.LoginFormsProvider;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.protocol.LoginProtocol;
+import org.keycloak.services.Urls;
+import org.keycloak.services.managers.AuthenticationManager;
+import org.keycloak.services.managers.ClientSessionCode;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Response;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class TermsAndConditions implements RequiredActionProvider, RequiredActionFactory {
+
+    public static final String PROVIDER_ID = "terms_and_conditions";
+
+    public static class Resource {
+
+        public Resource(RequiredActionContext context) {
+            this.context = context;
+        }
+
+        protected RequiredActionContext context;
+
+        @POST
+        @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
+        public Response agree(final MultivaluedMap<String, String> formData)  throws URISyntaxException, IOException, FreeMarkerException {
+            if (formData.containsKey("cancel")) {
+                LoginProtocol protocol = context.getSession().getProvider(LoginProtocol.class, context.getClientSession().getAuthMethod());
+                protocol.setRealm(context.getRealm())
+                        .setHttpHeaders(context.getHttpRequest().getHttpHeaders())
+                        .setUriInfo(context.getUriInfo());
+                context.getEvent().error(Errors.REJECTED_BY_USER);
+                return protocol.consentDenied(context.getClientSession());
+            }
+            context.getUser().removeRequiredAction(PROVIDER_ID);
+            return AuthenticationManager.nextActionAfterAuthentication(context.getSession(), context.getUserSession(), context.getClientSession(), context.getConnection(), context.getHttpRequest(), context.getUriInfo(), context.getEvent());
+        }
+
+    }
+
+    @Override
+    public RequiredActionProvider 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;
+    }
+
+    @Override
+    public void evaluateTriggers(RequiredActionContext context) {
+
+    }
+
+    @Override
+    public Response invokeRequiredAction(RequiredActionContext context) {
+        return context.getSession().getProvider(LoginFormsProvider.class)
+                .setClientSessionCode(new ClientSessionCode(context.getRealm(), context.getClientSession()).getCode())
+                .createForm("terms.ftl", new HashMap<String, Object>());
+    }
+
+    @Override
+    public Object jaxrsService(RequiredActionContext context) {
+        return new Resource(context);
+    }
+
+    @Override
+    public String getDisplayText() {
+        return "Terms and Conditions";
+    }
+
+    @Override
+    public void close() {
+
+    }
+}
diff --git a/services/src/main/java/org/keycloak/authentication/actions/UpdatePassword.java b/services/src/main/java/org/keycloak/authentication/actions/UpdatePassword.java
index 23d5a93..57ff72b 100755
--- a/services/src/main/java/org/keycloak/authentication/actions/UpdatePassword.java
+++ b/services/src/main/java/org/keycloak/authentication/actions/UpdatePassword.java
@@ -87,6 +87,12 @@ public class UpdatePassword implements RequiredActionProvider, RequiredActionFac
     }
 
     @Override
+    public String getDisplayText() {
+        return "Update Password";
+    }
+
+
+    @Override
     public String getId() {
         return UserModel.RequiredAction.UPDATE_PASSWORD.name();
     }
diff --git a/services/src/main/java/org/keycloak/authentication/actions/UpdateProfile.java b/services/src/main/java/org/keycloak/authentication/actions/UpdateProfile.java
index 8ee36e9..d9aaa73 100755
--- a/services/src/main/java/org/keycloak/authentication/actions/UpdateProfile.java
+++ b/services/src/main/java/org/keycloak/authentication/actions/UpdateProfile.java
@@ -69,6 +69,12 @@ public class UpdateProfile implements RequiredActionProvider, RequiredActionFact
     }
 
     @Override
+    public String getDisplayText() {
+        return "Update Profile";
+    }
+
+
+    @Override
     public String getId() {
         return UserModel.RequiredAction.UPDATE_PROFILE.name();
     }
diff --git a/services/src/main/java/org/keycloak/authentication/actions/UpdateTotp.java b/services/src/main/java/org/keycloak/authentication/actions/UpdateTotp.java
index 7f2228a..e378942 100755
--- a/services/src/main/java/org/keycloak/authentication/actions/UpdateTotp.java
+++ b/services/src/main/java/org/keycloak/authentication/actions/UpdateTotp.java
@@ -77,6 +77,12 @@ public class UpdateTotp implements RequiredActionProvider, RequiredActionFactory
     }
 
     @Override
+    public String getDisplayText() {
+        return "Configure Totp";
+    }
+
+
+    @Override
     public String getId() {
         return UserModel.RequiredAction.CONFIGURE_TOTP.name();
     }
diff --git a/services/src/main/java/org/keycloak/authentication/actions/VerifyEmail.java b/services/src/main/java/org/keycloak/authentication/actions/VerifyEmail.java
index 1be48a0..9d337f9 100755
--- a/services/src/main/java/org/keycloak/authentication/actions/VerifyEmail.java
+++ b/services/src/main/java/org/keycloak/authentication/actions/VerifyEmail.java
@@ -97,6 +97,12 @@ public class VerifyEmail implements RequiredActionProvider, RequiredActionFactor
     }
 
     @Override
+    public String getDisplayText() {
+        return "Verify Email";
+    }
+
+
+    @Override
     public String getId() {
         return UserModel.RequiredAction.VERIFY_EMAIL.name();
     }
diff --git a/services/src/main/java/org/keycloak/authentication/RequiredActionFactory.java b/services/src/main/java/org/keycloak/authentication/RequiredActionFactory.java
index 9acfcd1..b049781 100755
--- a/services/src/main/java/org/keycloak/authentication/RequiredActionFactory.java
+++ b/services/src/main/java/org/keycloak/authentication/RequiredActionFactory.java
@@ -7,4 +7,5 @@ import org.keycloak.provider.ProviderFactory;
  * @version $Revision: 1 $
  */
 public interface RequiredActionFactory extends ProviderFactory<RequiredActionProvider> {
+    String getDisplayText();
 }
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 b27ce49..2410331 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
@@ -5,6 +5,8 @@ import org.jboss.resteasy.annotations.cache.NoCache;
 import org.jboss.resteasy.spi.NotFoundException;
 import org.jboss.resteasy.spi.ResteasyProviderFactory;
 import org.keycloak.ClientConnection;
+import org.keycloak.authentication.RequiredActionFactory;
+import org.keycloak.authentication.RequiredActionProvider;
 import org.keycloak.events.Event;
 import org.keycloak.events.EventQuery;
 import org.keycloak.events.EventStoreProvider;
@@ -24,6 +26,7 @@ import org.keycloak.models.cache.CacheUserProvider;
 import org.keycloak.models.utils.ModelToRepresentation;
 import org.keycloak.models.utils.RepresentationToModel;
 import org.keycloak.protocol.oidc.TokenManager;
+import org.keycloak.provider.ProviderFactory;
 import org.keycloak.representations.adapters.action.GlobalRequestResult;
 import org.keycloak.representations.idm.RealmEventsConfigRepresentation;
 import org.keycloak.representations.idm.RealmRepresentation;
@@ -552,4 +555,19 @@ public class RealmAdminResource {
     public IdentityProvidersResource getIdentityProviderResource() {
         return new IdentityProvidersResource(realm, session, this.auth, adminEvent);
     }
+
+    @Path("required-actions")
+    @GET
+    @Produces(MediaType.APPLICATION_JSON)
+    public List<Map<String, String>> getRequiredActions() {
+        List<Map<String, String>> list = new LinkedList<>();
+        for (ProviderFactory factory : session.getKeycloakSessionFactory().getProviderFactories(RequiredActionProvider.class)) {
+            RequiredActionFactory actionFactory = (RequiredActionFactory)factory;
+            Map<String, String> data = new HashMap<>();
+            data.put("id", actionFactory.getId());
+            data.put("text", actionFactory.getDisplayText());
+            list.add(data);
+        }
+        return list;
+    }
 }
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
index 5714047..a5c8d3d 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
@@ -5,6 +5,8 @@ import org.jboss.resteasy.annotations.cache.NoCache;
 import org.jboss.resteasy.spi.BadRequestException;
 import org.jboss.resteasy.spi.NotFoundException;
 import org.keycloak.ClientConnection;
+import org.keycloak.authentication.RequiredActionFactory;
+import org.keycloak.authentication.RequiredActionProvider;
 import org.keycloak.email.EmailException;
 import org.keycloak.email.EmailProvider;
 import org.keycloak.events.admin.OperationType;
@@ -27,6 +29,7 @@ import org.keycloak.models.utils.RepresentationToModel;
 import org.keycloak.protocol.oidc.OIDCLoginProtocol;
 import org.keycloak.protocol.oidc.TokenManager;
 import org.keycloak.protocol.oidc.utils.RedirectUtils;
+import org.keycloak.provider.ProviderFactory;
 import org.keycloak.representations.idm.ClientMappingsRepresentation;
 import org.keycloak.representations.idm.CredentialRepresentation;
 import org.keycloak.representations.idm.FederatedIdentityRepresentation;
@@ -200,11 +203,15 @@ public class UsersResource {
         List<String> reqActions = rep.getRequiredActions();
 
         if (reqActions != null) {
-            for (UserModel.RequiredAction ra : UserModel.RequiredAction.values()) {
-                if (reqActions.contains(ra.name())) {
-                    user.addRequiredAction(ra);
+            Set<String> allActions = new HashSet<>();
+            for (ProviderFactory factory : session.getKeycloakSessionFactory().getProviderFactories(RequiredActionProvider.class)) {
+                allActions.add(factory.getId());
+            }
+            for (String action : allActions) {
+                if (reqActions.contains(action)) {
+                    user.addRequiredAction(action);
                 } else {
-                    user.removeRequiredAction(ra);
+                    user.removeRequiredAction(action);
                 }
             }
         }
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 08327b9..f4867b3 100755
--- a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
+++ b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
@@ -25,6 +25,8 @@ import org.jboss.logging.Logger;
 import org.jboss.resteasy.spi.HttpRequest;
 import org.keycloak.ClientConnection;
 import org.keycloak.authentication.AuthenticationProcessor;
+import org.keycloak.authentication.RequiredActionContext;
+import org.keycloak.authentication.RequiredActionProvider;
 import org.keycloak.email.EmailException;
 import org.keycloak.email.EmailProvider;
 import org.keycloak.events.Details;
@@ -67,7 +69,9 @@ import javax.ws.rs.Consumes;
 import javax.ws.rs.GET;
 import javax.ws.rs.POST;
 import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
 import javax.ws.rs.QueryParam;
+import javax.ws.rs.WebApplicationException;
 import javax.ws.rs.core.Context;
 import javax.ws.rs.core.Cookie;
 import javax.ws.rs.core.HttpHeaders;
@@ -303,12 +307,8 @@ public class LoginActionsService {
         event.detail(Details.CODE_ID, clientSession.getId());
 
         if (!clientCode.isValid(ClientSessionModel.Action.AUTHENTICATE.name()) || clientSession.getUserSession() != null) {
-            clientCode.setAction(ClientSessionModel.Action.AUTHENTICATE.name());
             event.client(clientSession.getClient()).error(Errors.EXPIRED_CODE);
-            return session.getProvider(LoginFormsProvider.class)
-                    .setError(Messages.EXPIRED_CODE)
-                    .setClientSessionCode(clientCode.getCode())
-                    .createLogin();
+            return ErrorPage.error(session, Messages.EXPIRED_CODE);
         }
 
         ClientModel client = clientSession.getClient();
@@ -685,11 +685,11 @@ public class LoginActionsService {
         }
         event.session(userSession);
 
-        LoginProtocol protocol = session.getProvider(LoginProtocol.class, clientSession.getAuthMethod());
-        protocol.setRealm(realm)
-                .setHttpHeaders(headers)
-                .setUriInfo(uriInfo);
         if (formData.containsKey("cancel")) {
+            LoginProtocol protocol = session.getProvider(LoginProtocol.class, clientSession.getAuthMethod());
+            protocol.setRealm(realm)
+                    .setHttpHeaders(headers)
+                    .setUriInfo(uriInfo);
             event.error(Errors.REJECTED_BY_USER);
             return protocol.consentDenied(clientSession);
         }
@@ -1075,4 +1075,111 @@ public class LoginActionsService {
             }
         }
     }
+
+    @Path("required-actions/{action}")
+    public Object requiredAction(@QueryParam("code") String code,
+                                 @PathParam("action") String action) {
+        event.event(EventType.LOGIN);
+        if (!checkSsl()) {
+            event.error(Errors.SSL_REQUIRED);
+            throw new WebApplicationException(ErrorPage.error(session, Messages.HTTPS_REQUIRED));
+        }
+
+        if (!realm.isEnabled()) {
+            event.error(Errors.REALM_DISABLED);
+            return ErrorPage.error(session, Messages.REALM_NOT_ENABLED);
+        }
+        ClientSessionCode clientCode = ClientSessionCode.parse(code, session, realm);
+        if (clientCode == null) {
+            event.error(Errors.INVALID_CODE);
+            throw new WebApplicationException(ErrorPage.error(session, Messages.INVALID_CODE));
+        }
+
+        final ClientSessionModel clientSession = clientCode.getClientSession();
+        event.detail(Details.CODE_ID, clientSession.getId());
+
+        /*
+        if (!clientCode.isValid(ClientSessionModel.Action.AUTHENTICATE.name()) || clientSession.getUserSession() != null) {
+            event.client(clientSession.getClient()).error(Errors.EXPIRED_CODE);
+            throw new WebApplicationException(ErrorPage.error(session, Messages.EXPIRED_CODE));
+        }
+        */
+
+        ClientModel client = clientSession.getClient();
+        if (client == null) {
+            event.error(Errors.CLIENT_NOT_FOUND);
+            throw new WebApplicationException( ErrorPage.error(session, Messages.UNKNOWN_LOGIN_REQUESTER));
+        }
+        session.getContext().setClient(client);
+
+        if (!client.isEnabled()) {
+            event.error(Errors.CLIENT_NOT_FOUND);
+            throw new WebApplicationException( ErrorPage.error(session, Messages.LOGIN_REQUESTER_NOT_ENABLED));
+        }
+
+        if (action == null) {
+            logger.error("required action was null");
+            event.error(Errors.INVALID_CODE);
+            throw new WebApplicationException(ErrorPage.error(session, Messages.INVALID_CODE));
+
+        }
+
+        RequiredActionProvider provider = session.getProvider(RequiredActionProvider.class, action);
+        if (provider == null) {
+            logger.error("required action provider was null");
+            event.error(Errors.INVALID_CODE);
+            throw new WebApplicationException(ErrorPage.error(session, Messages.INVALID_CODE));
+        }
+        RequiredActionContext context = new RequiredActionContext() {
+            @Override
+            public EventBuilder getEvent() {
+                return event;
+            }
+
+            @Override
+            public UserModel getUser() {
+                return getUserSession().getUser();
+            }
+
+            @Override
+            public RealmModel getRealm() {
+                return realm;
+            }
+
+            @Override
+            public ClientSessionModel getClientSession() {
+                return clientSession;
+            }
+
+            @Override
+            public UserSessionModel getUserSession() {
+                return clientSession.getUserSession();
+            }
+
+            @Override
+            public ClientConnection getConnection() {
+                return clientConnection;
+            }
+
+            @Override
+            public UriInfo getUriInfo() {
+                return uriInfo;
+            }
+
+            @Override
+            public KeycloakSession getSession() {
+                return session;
+            }
+
+            @Override
+            public HttpRequest getHttpRequest() {
+                return request;
+            }
+        };
+        return provider.jaxrsService(context);
+
+
+
+    }
+
 }
diff --git a/services/src/main/java/org/keycloak/services/resources/RealmsResource.java b/services/src/main/java/org/keycloak/services/resources/RealmsResource.java
index b3dd570..dc674f7 100755
--- a/services/src/main/java/org/keycloak/services/resources/RealmsResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/RealmsResource.java
@@ -4,6 +4,7 @@ import org.jboss.logging.Logger;
 import org.jboss.resteasy.spi.NotFoundException;
 import org.jboss.resteasy.spi.ResteasyProviderFactory;
 import org.keycloak.ClientConnection;
+import org.keycloak.authentication.RequiredActionProvider;
 import org.keycloak.events.EventBuilder;
 import org.keycloak.models.ClientModel;
 import org.keycloak.models.Constants;
diff --git a/services/src/main/java/org/keycloak/services/Urls.java b/services/src/main/java/org/keycloak/services/Urls.java
index 93b479b..2adbd1e 100755
--- a/services/src/main/java/org/keycloak/services/Urls.java
+++ b/services/src/main/java/org/keycloak/services/Urls.java
@@ -128,15 +128,20 @@ public class Urls {
     }
 
     public static URI loginActionUpdatePassword(URI baseUri, String realmId) {
-        return requiredActionsBase(baseUri).path(LoginActionsService.class, "updatePassword").build(realmId);
+        return loginActionsBase(baseUri).path(LoginActionsService.class, "updatePassword").build(realmId);
     }
 
     public static URI loginActionUpdateTotp(URI baseUri, String realmId) {
-        return requiredActionsBase(baseUri).path(LoginActionsService.class, "updateTotp").build(realmId);
+        return loginActionsBase(baseUri).path(LoginActionsService.class, "updateTotp").build(realmId);
     }
 
+    public static UriBuilder requiredActionBase(URI baseUri) {
+        return loginActionsBase(baseUri).path(LoginActionsService.class, "requiredAction");
+    }
+
+
     public static URI loginActionUpdateProfile(URI baseUri, String realmId) {
-        return requiredActionsBase(baseUri).path(LoginActionsService.class, "updateProfile").build(realmId);
+        return loginActionsBase(baseUri).path(LoginActionsService.class, "updateProfile").build(realmId);
     }
 
     public static URI loginActionEmailVerification(URI baseUri, String realmId) {
@@ -144,7 +149,7 @@ public class Urls {
     }
 
     public static UriBuilder loginActionEmailVerificationBuilder(URI baseUri) {
-        return requiredActionsBase(baseUri).path(LoginActionsService.class, "emailVerification");
+        return loginActionsBase(baseUri).path(LoginActionsService.class, "emailVerification");
     }
 
     public static URI loginPasswordReset(URI baseUri, String realmId) {
@@ -152,7 +157,7 @@ public class Urls {
     }
 
     public static UriBuilder loginPasswordResetBuilder(URI baseUri) {
-        return requiredActionsBase(baseUri).path(LoginActionsService.class, "passwordReset");
+        return loginActionsBase(baseUri).path(LoginActionsService.class, "passwordReset");
     }
 
     public static URI loginUsernameReminder(URI baseUri, String realmId) {
@@ -160,7 +165,7 @@ public class Urls {
     }
 
     public static UriBuilder loginUsernameReminderBuilder(URI baseUri) {
-        return requiredActionsBase(baseUri).path(LoginActionsService.class, "usernameReminder");
+        return loginActionsBase(baseUri).path(LoginActionsService.class, "usernameReminder");
     }
 
     public static String realmIssuer(URI baseUri, String realmId) {
@@ -172,11 +177,11 @@ public class Urls {
     }
 
     public static URI realmLoginAction(URI baseUri, String realmId) {
-        return requiredActionsBase(baseUri).path(LoginActionsService.class, "processLogin").build(realmId);
+        return loginActionsBase(baseUri).path(LoginActionsService.class, "processLogin").build(realmId);
     }
 
     public static URI realmLoginPage(URI baseUri, String realmId) {
-        return requiredActionsBase(baseUri).path(LoginActionsService.class, "loginPage").build(realmId);
+        return loginActionsBase(baseUri).path(LoginActionsService.class, "loginPage").build(realmId);
     }
 
     private static UriBuilder realmLogout(URI baseUri) {
@@ -184,11 +189,11 @@ public class Urls {
     }
 
     public static URI realmRegisterAction(URI baseUri, String realmId) {
-        return requiredActionsBase(baseUri).path(LoginActionsService.class, "processRegister").build(realmId);
+        return loginActionsBase(baseUri).path(LoginActionsService.class, "processRegister").build(realmId);
     }
 
     public static URI realmRegisterPage(URI baseUri, String realmId) {
-        return requiredActionsBase(baseUri).path(LoginActionsService.class, "registerPage").build(realmId);
+        return loginActionsBase(baseUri).path(LoginActionsService.class, "registerPage").build(realmId);
     }
 
     public static URI realmInstalledAppUrnCallback(URI baseUri, String realmId) {
@@ -196,7 +201,7 @@ public class Urls {
     }
 
     public static URI realmOauthAction(URI baseUri, String realmId) {
-        return requiredActionsBase(baseUri).path(LoginActionsService.class, "processConsent").build(realmId);
+        return loginActionsBase(baseUri).path(LoginActionsService.class, "processConsent").build(realmId);
     }
 
     public static String localeCookiePath(URI baseUri, String realmName){
@@ -207,7 +212,7 @@ public class Urls {
         return themeBase(baseUri).path(Version.RESOURCES_VERSION).build();
     }
 
-    private static UriBuilder requiredActionsBase(URI baseUri) {
+    private static UriBuilder loginActionsBase(URI baseUri) {
         return realmBase(baseUri).path(RealmsResource.class, "getLoginActionsService");
     }
 
diff --git a/services/src/main/resources/META-INF/services/org.keycloak.authentication.RequiredActionFactory b/services/src/main/resources/META-INF/services/org.keycloak.authentication.RequiredActionFactory
index fc74174..8106ec7 100755
--- a/services/src/main/resources/META-INF/services/org.keycloak.authentication.RequiredActionFactory
+++ b/services/src/main/resources/META-INF/services/org.keycloak.authentication.RequiredActionFactory
@@ -1,4 +1,5 @@
 org.keycloak.authentication.actions.UpdatePassword
 org.keycloak.authentication.actions.UpdateProfile
 org.keycloak.authentication.actions.UpdateTotp
-org.keycloak.authentication.actions.VerifyEmail
\ No newline at end of file
+org.keycloak.authentication.actions.VerifyEmail
+org.keycloak.authentication.actions.TermsAndConditions
\ No newline at end of file
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LoginTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LoginTest.java
index d6f041f..d84b8f9 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LoginTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LoginTest.java
@@ -40,6 +40,7 @@ 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.rule.KeycloakRule;
@@ -98,6 +99,9 @@ public class LoginTest {
 
     @WebResource
     protected LoginPage loginPage;
+
+    @WebResource
+    protected ErrorPage errorPage;
     
     @WebResource
     protected LoginPasswordUpdatePage updatePasswordPage;
@@ -424,8 +428,10 @@ public class LoginTest {
             Time.setOffset(5000);
             loginPage.login("login@test.com", "password");
 
-            loginPage.assertCurrent();
-            Assert.assertEquals("Login timeout. Please login again.", loginPage.getError());
+            //loginPage.assertCurrent();
+            errorPage.assertCurrent();
+
+            //Assert.assertEquals("Login timeout. Please login again.", loginPage.getError());
 
             events.expectLogin().user((String) null).session((String) null).error("expired_code").clearDetails().detail(Details.CODE_ID, AssertEvents.isCodeId()).assertEvent();