keycloak-uncached

Changes

Details

diff --git a/admin-ui/src/main/resources/META-INF/resources/admin/index.html b/admin-ui/src/main/resources/META-INF/resources/admin/index.html
index e062336..2f17098 100755
--- a/admin-ui/src/main/resources/META-INF/resources/admin/index.html
+++ b/admin-ui/src/main/resources/META-INF/resources/admin/index.html
@@ -48,7 +48,11 @@
     <script src="js/loaders.js"></script>
     <script src="js/services.js"></script>
 
-
+    <style>
+        [ng\:cloak], [ng-cloak], .ng-cloak {
+            display: none !important;
+        }
+    </style>
 </head>
 
 <body class="admin-console" data-ng-controller="GlobalCtrl" ng-cloak>
diff --git a/admin-ui/src/main/resources/META-INF/resources/admin/js/app.js b/admin-ui/src/main/resources/META-INF/resources/admin/js/app.js
index 90bc5ff..f618052 100755
--- a/admin-ui/src/main/resources/META-INF/resources/admin/js/app.js
+++ b/admin-ui/src/main/resources/META-INF/resources/admin/js/app.js
@@ -535,6 +535,7 @@ module.directive('onoffswitch', function() {
         replace: true,
         scope: {
             ngModel: '=',
+            ngDisabled: '=',
             ngBind: '=',
             name: '=',
             id: '=',
@@ -544,9 +545,11 @@ module.directive('onoffswitch', function() {
         compile: function(element, attrs) {
             if (!attrs.onText) { attrs.onText = "ON"; }
             if (!attrs.offText) { attrs.offText = "OFF"; }
+            if (!attrs.ngDisabled) { attrs.ngDisabled = false; }
 
-            var html = "<div class=\"onoffswitch\">" +
-                "<input type=\"checkbox\" data-ng-model=\"ngModel\" class=\"onoffswitch-checkbox\" name=\"" + attrs.name + "\" id=\"" + attrs.id + "\">" +
+            var html = "<span><div class=\"onoffswitch\" data-ng-class=\"{disabled: ngDisabled}\">" +
+                "<input type=\"checkbox\" data-ng-model=\"ngModel\" ng-disabled=\"ngDisabled\"" +
+                " class=\"onoffswitch-checkbox\" name=\"" + attrs.name + "\" id=\"" + attrs.id + "\">" +
                 "<label for=\"" + attrs.id + "\" class=\"onoffswitch-label\">" +
                 "<span class=\"onoffswitch-inner\">" +
                 "<span class=\"onoffswitch-active\">{{onText}}</span>" +
@@ -554,14 +557,13 @@ module.directive('onoffswitch', function() {
                 "</span>" +
                 "<span class=\"onoffswitch-switch\"></span>" +
                 "</label>" +
-                "</div>";
+                "</div></span>";
 
             element.replaceWith($(html));
         }
     }
 });
 
-
 module.directive('kcInput', function() {
     var d = {
         scope : true,
diff --git a/admin-ui/src/main/resources/META-INF/resources/admin/js/controllers/realm.js b/admin-ui/src/main/resources/META-INF/resources/admin/js/controllers/realm.js
index fffbfc0..6598992 100755
--- a/admin-ui/src/main/resources/META-INF/resources/admin/js/controllers/realm.js
+++ b/admin-ui/src/main/resources/META-INF/resources/admin/js/controllers/realm.js
@@ -181,13 +181,131 @@ module.controller('RealmRequiredCredentialsCtrl', function($scope, Realm, realm,
         registrationAllowed : realm.registrationAllowed
     };
 
+    if (realm.hasOwnProperty('passwordPolicy')){
+        $scope.realm['passwordPolicy'] = realm.passwordPolicy;
+    } else {
+        $scope.realm['passwordPolicy'] = "";
+        realm['passwordPolicy'] = "";
+    }
+
+    var oldCopy = angular.copy($scope.realm);
+
+    /* Map used in the table when hovering over (i) icon */
+    $scope.policyMessages = {
+        length:         "Minimal password length. Default value is 8.",
+        digits:         "Minimal number of digits in password. Default value is 1.",
+        lowerCase:      "Minimal number of lowercase characters in password. Default value is 1.",
+        upperCase:      "Minimal number of uppercase characters in password. Default value is 1.",
+        specialChars:   "Minimal number of special characters in password. Default value is 1."
+    }
+
+    // $scope.policy is an object representing passwordPolicy string
+    $scope.policy = {};
+    // All available policies
+    $scope.allPolicies = ['length', 'digits', 'lowerCase', 'upperCase', 'specialChars'];
+    // List of configured policies
+    $scope.configuredPolicies = [];
+    // List of not configured policies
+    $scope.availablePolicies = $scope.allPolicies.slice(0);
+
+    $scope.addPolicy = function(){
+        $scope.policy[$scope.newPolicyId] = "";
+        updateConfigured();
+    }
+
+    $scope.removePolicy = function(pId){
+        delete $scope.policy[pId];
+        updateConfigured();
+    }
+
+    // Updating lists of configured and non-configured policies based on the $scope.policy object
+    var updateConfigured = function(){
+
+        for (var i = 0; i < $scope.allPolicies.length; i++){
+
+            var policy = $scope.allPolicies[i];
+
+            if($scope.policy.hasOwnProperty(policy)){
+
+                var ind = $scope.configuredPolicies.indexOf(policy);
+
+                if(ind < 0){
+                    $scope.configuredPolicies.push(policy);
+                }
+
+                ind = $scope.availablePolicies.indexOf(policy);
+                if(ind > -1){
+                    $scope.availablePolicies.splice(ind, 1);
+                }
+            } else {
+
+                var ind = $scope.configuredPolicies.indexOf(policy);
+
+                if(ind > -1){
+                    $scope.configuredPolicies.splice(ind, 1);
+                }
+
+                ind = $scope.availablePolicies.indexOf(policy);
+                if(ind < 0){
+                    $scope.availablePolicies.push(policy);
+                }
+            }
+        }
+
+        if ($scope.availablePolicies.length > 0){
+            $scope.newPolicyId = $scope.availablePolicies[0];
+        }
+    }
+
+    // Creating object from policy string
+    var evaluatePolicy = function(policyString){
+
+        var policyObject = {};
+
+        if (!policyString || policyString.length == 0){
+            return policyObject;
+        }
+
+        var policyArray = policyString.split(" and ");
+
+        for (var i = 0; i < policyArray.length; i ++){
+            var policyToken = policyArray[i];
+            var re = /(\w+)\(*(\d*)\)*/;
+
+            var policyEntry = re.exec(policyToken);
+            policyObject[policyEntry[1]] = policyEntry[2];
+        }
+
+        return policyObject;
+    }
+
+    // Creating policy string based on policy object
+    var generatePolicy = function(policyObject){
+        var policyString = "";
+
+        for (var key in policyObject){
+            policyString += key;
+            var value = policyObject[key];
+            if ( value != ""){
+                policyString += "("+value+")";
+            }
+            policyString += " and ";
+        }
+
+        policyString = policyString.substring(0, policyString.length - 5);
+
+        return policyString;
+    }
+
+    $scope.policy = evaluatePolicy(realm.passwordPolicy);
+    updateConfigured();
+
     $scope.userCredentialOptions = {
         'multiple' : true,
         'simple_tags' : true,
         'tags' : ['password', 'totp', 'cert']
     };
 
-    var oldCopy = angular.copy($scope.realm);
     $scope.changed = false;
 
     $scope.$watch('realm', function() {
@@ -196,17 +314,31 @@ module.controller('RealmRequiredCredentialsCtrl', function($scope, Realm, realm,
         }
     }, true);
 
+    $scope.$watch('policy', function() {
+        $scope.realm.passwordPolicy = generatePolicy($scope.policy);
+        if ($scope.realm.passwordPolicy != realm.passwordPolicy){
+            $scope.changed = true;
+        }
+    }, true);
+
     $scope.save = function() {
-        var realmCopy = angular.copy($scope.realm);
         $scope.changed = false;
-        Realm.update(realmCopy, function () {
+
+        Realm.update($scope.realm, function () {
             $location.url("/realms/" + realm.id + "/required-credentials");
             Notifications.success("Your changes have been saved to the realm.");
+            oldCopy = angular.copy($scope.realm);
         });
     };
 
     $scope.reset = function() {
         $scope.realm = angular.copy(oldCopy);
+
+        $scope.configuredPolicies = [];
+        $scope.availablePolicies = $scope.allPolicies.slice(0);
+        $scope.policy = evaluatePolicy(oldCopy.passwordPolicy);
+        updateConfigured();
+
         $scope.changed = false;
     };
 });
@@ -430,7 +562,7 @@ module.controller('RealmSocialCtrl', function($scope, realm, Realm, $location, N
             $scope.realm.socialProviders[$scope.newProviderId+".key"]="";
             $scope.realm.socialProviders[$scope.newProviderId+".secret"]="";
             $scope.configuredProviders.push($scope.newProviderId);
-            $scope.unsetProviders.remove($scope.unsetProviders.indexOf($scope.newProviderId));
+            $scope.unsetProviders.splice($scope.unsetProviders.indexOf($scope.newProviderId),1);
             selectFirstProvider();
         }
     };
@@ -438,12 +570,12 @@ module.controller('RealmSocialCtrl', function($scope, realm, Realm, $location, N
     $scope.removeProvider = function(pId) {
         delete $scope.realm.socialProviders[pId+".key"];
         delete $scope.realm.socialProviders[pId+".secret"];
-        $scope.configuredProviders.remove($scope.configuredProviders.indexOf(pId));
+        $scope.configuredProviders.splice($scope.configuredProviders.indexOf(pId),1);
 
         // Removing from postSaveProviders, so the empty fields are not red if the provider is added to the list again
         var rId = $scope.postSaveProviders.indexOf(pId);
         if (rId > -1){
-            $scope.postSaveProviders.remove(rId)
+            $scope.postSaveProviders.splice(rId,1)
         }
 
         $scope.unsetProviders.push(pId);
diff --git a/admin-ui/src/main/resources/META-INF/resources/admin/js/controllers/users.js b/admin-ui/src/main/resources/META-INF/resources/admin/js/controllers/users.js
index 0ae5b79..de73f63 100755
--- a/admin-ui/src/main/resources/META-INF/resources/admin/js/controllers/users.js
+++ b/admin-ui/src/main/resources/META-INF/resources/admin/js/controllers/users.js
@@ -231,20 +231,27 @@ module.controller('UserCredentialsCtrl', function($scope, realm, user, User, Use
     $scope.realm = realm;
     $scope.user = angular.copy(user);
 
+    $scope.isTotp = false;
+    if(!!user.totp){
+        $scope.isTotp = user.totp;
+    }
+
     $scope.resetPassword = function() {
 
-        if ($scope.password != $scope.confirmPassword) {
-            Notifications.error("Password and confirmation does not match.");
-            $scope.password = "";
-            $scope.confirmPassword = "";
-            return;
-        }
+        if ($scope.pwdChange) {
+            if ($scope.password != $scope.confirmPassword) {
+                Notifications.error("Password and confirmation does not match.");
+                $scope.password = "";
+                $scope.confirmPassword = "";
+                return;
+            }
 
-        if (!$scope.user.hasOwnProperty('requiredActions')){
-            $scope.user.requiredActions = [];
-        }
-        if ($scope.user.requiredActions.indexOf("UPDATE_PASSWORD") < 0){
-            $scope.user.requiredActions.push("UPDATE_PASSWORD");
+            if (!$scope.user.hasOwnProperty('requiredActions')){
+                $scope.user.requiredActions = [];
+            }
+            if ($scope.user.requiredActions.indexOf("UPDATE_PASSWORD") < 0){
+                $scope.user.requiredActions.push("UPDATE_PASSWORD");
+            }
         }
 
         var credentials = [ { type : "password", value : $scope.password } ];
@@ -254,21 +261,65 @@ module.controller('UserCredentialsCtrl', function($scope, realm, user, User, Use
             userId: $scope.user.username
         }, $scope.user, function () {
 
-            UserCredentials.update({
-                realm: realm.id,
-                userId: $scope.user.username
-            }, credentials, function () {
-                Notifications.success("The user password has been reset. The user is required to change his password on" +
-                    " the next login.");
-            }, function () {
-                Notifications.error("Error while resetting user password. Be aware that the update password required action" +
-                    " was already set.");
-            });
+            $scope.isTotp = $scope.user.totp;
+
+            if ($scope.pwdChange){
+                UserCredentials.update({
+                    realm: realm.id,
+                    userId: $scope.user.username
+                }, credentials, function () {
+                    Notifications.success("The password has been reset. The user is required to change his password on" +
+                        " the next login.");
+                    $scope.password = "";
+                    $scope.confirmPassword = "";
+                    $scope.pwdChange = false;
+                    $scope.isTotp = user.totp;
+                    $scope.userChange = false;
+                }, function () {
+                    Notifications.error("Error while resetting user password. Be aware that the update password required action" +
+                        " was already set.");
+                });
+            } else {
+                Notifications.success("User settings was updated.");
+                $scope.isTotp = user.totp;
+                $scope.userChange = false;
+            }
 
         }, function () {
-            Notifications.error("Error while adding update password required action. Password was not reset.");
+            Notifications.error("Error while updating user settings.");
         });
     };
+
+    $scope.$watch('user', function() {
+        if (!angular.equals($scope.user, user)) {
+            $scope.userChange = true;
+        } else {
+            $scope.userChange = false;
+        }
+    }, true);
+
+    $scope.$watch('password', function() {
+        if (!!$scope.password){
+            $scope.pwdChange = true;
+        } else {
+            $scope.pwdChange = false;
+        }
+    }, true);
+
+    $scope.reset = function() {
+        $scope.password = "";
+        $scope.confirmPassword = "";
+
+        $scope.user = angular.copy(user);
+
+        $scope.isTotp = false;
+        if(!!user.totp){
+            $scope.isTotp = user.totp;
+        }
+
+        $scope.pwdChange = false;
+        $scope.userChange = false;
+    };
 });
 
 module.controller('RoleMappingCtrl', function($scope, realm, User, users, role, RoleMapping, Notifications) {
diff --git a/admin-ui/src/main/resources/META-INF/resources/admin/partials/realm-credentials.html b/admin-ui/src/main/resources/META-INF/resources/admin/partials/realm-credentials.html
index 3b3c940..9ddead0 100755
--- a/admin-ui/src/main/resources/META-INF/resources/admin/partials/realm-credentials.html
+++ b/admin-ui/src/main/resources/META-INF/resources/admin/partials/realm-credentials.html
@@ -1,4 +1,4 @@
-<div id="wrapper" class="container">
+<div id="wrapper" class="container realm-policy">
     <div class="row">
         <div class="bs-sidebar col-md-3 clearfix" data-ng-include data-src="'partials/realm-menu.html'"></div>
         <div id="content-area" class="col-md-9" role="main">
@@ -23,6 +23,7 @@
                 <h2><span>{{realm.realm}}</span> Credentials</h2>
                 <form name="realmForm" novalidate>
                     <fieldset class="border-top">
+                        <legend uncollapsed><span class="text">Realm Credentials Settings</span></legend>
                         <div class="form-group clearfix">
                             <label for="user" class="control-label two-lines">Required User Credentials</label>
 
@@ -45,16 +46,84 @@
                             </div>
                         </div>
                     </fieldset>
-                   <div class="form-actions">
+                    <fieldset class="border-top">
+                        <legend uncollapsed><span class="text">Realm Password Policy</span></legend>
+                        <div class="form-group clearfix">
+                        <table>
+                            <caption class="hidden">Table of Password Policies</caption>
+                            <thead>
+                            <tr ng-show="availablePolicies.length > 0">
+                                <th colspan="5" class="rcue-table-actions">
+                                    <div class="actions">
+                                        <div class="select-rcue">
+                                            <select ng-model="newPolicyId"
+                                                    ng-options="name as name for name in availablePolicies"
+                                                    placeholder="Please select">
+                                            </select>
+                                        </div>
+                                        <div>
+                                            <button ng-click="addPolicy()" ng-disabled="">Add Policy</button>
+                                        </div>
+                                    </div>
+                                </th>
+                            </tr>
+                            <tr>
+                                <th>Policy Type</th>
+                                <th>Policy Value</th>
+                                <th>Actions</th>
+                            </tr>
+                            </thead>
+                            <tbody>
+                            <tr ng-repeat="name in configuredPolicies">
+                                <td>
+                                    <div class="clearfix">
+                                        <input class="input-small disabled" type="text" value="{{name}}" readonly>
+                                    </div>
+                                </td>
+                                <td>
+                                    <input ng-model="policy[name]" type="number" placeholder="No value assigned" class="input-small">
+                                </td>
+                                <td>
+                                    <div class="action-div"><i class="icon-question" popover="{{policyMessages[name]}}"
+                                                               popover-placement="left" popover-trigger="mouseenter"></i></div>
+                                    <div class="action-div"><i class="icon-remove" ng-click="removePolicy(name)"></i></div>
+                                </td>
+                            </tr>
+                            </tbody>
+                        </table>
+                            </div>
+                    </fieldset>
+                    <div class="form-actions">
                         <button type="submit" kc-save class="primary" data-ng-show="changed">Save
                         </button>
                         <button type="submit" kc-reset data-ng-show="changed">Clear changes
                         </button>
                     </div>
-
                 </form>
             </div>
         </div>
         <div id="container-right-bg"></div>
     </div>
-</div>
\ No newline at end of file
+</div>
+
+<!-- TODO remove once this page is properly styled -->
+<style type="text/css">
+    .realm-policy .actions > div {
+        display: inline-block;
+        overflow: hidden;
+    }
+
+    .realm-policy td {
+        font-size: 10px;
+    }
+
+    .realm-policy .action-div {
+        display: inline-block;
+        margin: 5px;
+    }
+
+    .realm-policy .icon-remove, .realm-policy .icon-question {
+        cursor: pointer;
+    }
+
+</style>
\ No newline at end of file
diff --git a/admin-ui/src/main/resources/META-INF/resources/admin/partials/realm-menu.html b/admin-ui/src/main/resources/META-INF/resources/admin/partials/realm-menu.html
index 40739e8..158a787 100755
--- a/admin-ui/src/main/resources/META-INF/resources/admin/partials/realm-menu.html
+++ b/admin-ui/src/main/resources/META-INF/resources/admin/partials/realm-menu.html
@@ -1,5 +1,7 @@
 <ul data-ng-hide="createRealm">
-    <li data-ng-class="(!path[2] || path[1] == 'role' || path[2] == 'roles' || path[2] == 'token-settings' || path[2] == 'social-settings' || path[2] == 'required-credentials' || path[2] == 'smtp-settings') && 'active'"><a href="#/realms/{{realm.id}}">Settings</a></li>
+    <li data-ng-class="(!path[2] || path[1] == 'role' || path[2] == 'roles' || path[2] == 'token-settings' ||
+    path[2] == 'social-settings' || path[2] == 'required-credentials' || path[2] == 'registration-settings' ||
+    path[2] == 'keys-settings' || path[2] == 'smtp-settings') && 'active'"><a href="#/realms/{{realm.id}}">Settings</a></li>
     <li data-ng-class="(path[2] == 'users' || path[1] == 'user') && 'active'"><a href="#/realms/{{realm.id}}/users">Users</a>
     </li>
     <li data-ng-class="(path[2] == 'applications' || path[1] == 'application') && 'active'"><a href="#/realms/{{realm.id}}/applications">Applications</a></li>
diff --git a/admin-ui/src/main/resources/META-INF/resources/admin/partials/user-credentials.html b/admin-ui/src/main/resources/META-INF/resources/admin/partials/user-credentials.html
index a732945..8f3de02 100755
--- a/admin-ui/src/main/resources/META-INF/resources/admin/partials/user-credentials.html
+++ b/admin-ui/src/main/resources/META-INF/resources/admin/partials/user-credentials.html
@@ -14,10 +14,11 @@
                     <li><a href="#/realms/{{realm.id}}">{{realm.realm}}</a></li>
                     <li class="active">Users</li>
                 </ol>
-                <h2><span>{{user.username}}'s</span> Reset Password</h2>
+                <h2><span>{{user.username}}'s</span> Credentials</h2>
 
                 <form name="userForm" novalidate>
                     <fieldset class="border-top">
+                        <legend uncollapsed><span class="text">Reset Password</span></legend>
                         <div class="form-group">
                             <label for="password">New Password</label>
                             <div class="controls">
@@ -28,14 +29,20 @@
                         <div class="form-group">
                             <label class="two-lines" for="password">New Password Confirmation</label>
                             <div class="controls">
-                                <input type="password" id="confirmPassword" name="confirmPassword" data-ng-model="confirmPassword"
-                                       required>
+                                <input type="password" id="confirmPassword" name="confirmPassword"
+                                       data-ng-model="confirmPassword" required>
                             </div>
                         </div>
+                        <div class="form-group clearfix block" >
+                            <label for="userTotp" class="control-label">TOTP Enabled</label>
+                            <input ng-model="user.totp" name="userTotp" class="kokosak"  ng-disabled="!isTotp" id="userTotp" onoffswitch/>
+                        </div>
                     </fieldset>
                     <div class="form-actions">
-                        <button type="submit" class="primary" data-ng-click="resetPassword()"
-                                ng-show="password != null">Reset Password</button>
+                        <button type="submit" data-ng-click="resetPassword()" class="primary" data-ng-show="userChange && !pwdChange">Save</button>
+                        <button type="submit" data-ng-click="resetPassword()" class="primary" data-ng-show="!userChange && pwdChange">Reset Password</button>
+                        <button type="submit" data-ng-click="resetPassword()" class="primary" data-ng-show="userChange && pwdChange">Save and Reset Password</button>
+                        <button type="submit" kc-reset data-ng-show="userChange || pwdChange">Clear changes</button>
                     </div>
                 </form>
 
diff --git a/admin-ui/src/main/resources/META-INF/resources/admin/partials/user-detail.html b/admin-ui/src/main/resources/META-INF/resources/admin/partials/user-detail.html
index 13b60ce..0735afa 100755
--- a/admin-ui/src/main/resources/META-INF/resources/admin/partials/user-detail.html
+++ b/admin-ui/src/main/resources/META-INF/resources/admin/partials/user-detail.html
@@ -71,10 +71,6 @@
                             <input ng-model="user.enabled" name="userEnabled" id="userEnabled" onoffswitch />
                         </div>
                         <div class="form-group clearfix block">
-                            <label for="userTotp" class="control-label">TOTP Enabled</label>
-                            <input ng-model="user.totp" name="userTotp" id="userTotp" onoffswitch />
-                        </div>
-                        <div class="form-group clearfix block">
                             <label for="emailVerified" class="control-label">Email verified</label>
                             <input ng-model="user.emailVerified" name="emailVerified" id="emailVerified" onoffswitch />
                         </div>
diff --git a/admin-ui-styles/src/main/resources/META-INF/resources/admin-ui/css/forms.css b/admin-ui-styles/src/main/resources/META-INF/resources/admin-ui/css/forms.css
index 93b0fde..ef19364 100644
--- a/admin-ui-styles/src/main/resources/META-INF/resources/admin-ui/css/forms.css
+++ b/admin-ui-styles/src/main/resources/META-INF/resources/admin-ui/css/forms.css
@@ -13,12 +13,12 @@ select:-moz-focusring {
 input[type="text"],
 input[type="password"],
 input[type="email"],
+input[type="number"],
 textarea {
   font-size: 1.1em;
   padding: 0 0.545454545454545em;
   height: 2.36363636363636em;
   /* 26px */
-
   border: 1px #b6b6b6 solid;
   border-radius: 2px;
   box-shadow: inset 0px 2px 2px rgba(0, 0, 0, 0.1);
@@ -28,12 +28,14 @@ textarea {
 input[type="text"]:hover,
 input[type="password"]:hover,
 input[type="email"]:hover,
+input[type="number"]:hover,
 textarea:hover {
   border-color: #62afdb;
 }
 input[type="text"]:focus,
 input[type="password"]:focus,
 input[type="email"]:focus,
+input[type="number"]:focus,
 textarea:focus {
   border-color: #62afdb;
   box-shadow: #62afdb 0 0 5px;
@@ -41,6 +43,7 @@ textarea:focus {
 input[type="text"].error,
 input[type="password"].error,
 input[type="email"].error,
+input[type="number"].error,
 textarea.error {
   border-color: #ba1212;
   transition: all 0.33s ease-in-out;
@@ -50,36 +53,42 @@ textarea.error {
 input[type="text"].error:focus,
 input[type="password"].error:focus,
 input[type="email"].error:focus,
+input[type="number"].error:focus,
 textarea.error:focus {
   box-shadow: 0 0 5px #ba1212;
 }
 input[type="text"].tiny,
 input[type="password"].tiny,
 input[type="email"].tiny,
+input[type="number"].tiny,
 textarea.tiny {
   width: 4.54545454545455em;
 }
 input[type="text"].small,
 input[type="password"].small,
 input[type="email"].small,
+input[type="number"].small,
 textarea.small {
   width: 9.09090909090909em;
 }
 input[type="text"].medium,
 input[type="password"].medium,
 input[type="email"].medium,
+input[type="number"].medium,
 textarea.medium {
   width: 18.1818em;
 }
 input[type="text"].large,
 input[type="password"].large,
 input[type="email"].large,
+input[type="number"].large,
 textarea.large {
   width: 27.2727272727273em;
 }
 input[type="text"].xlarge,
 input[type="password"].xlarge,
 input[type="email"].xlarge,
+input[type="number"].xlarge,
 textarea.xlarge {
   width: 36.3636363636364em;
 }
@@ -90,18 +99,21 @@ textarea {
 input[type="text"][readonly],
 input[type="password"][readonly],
 input[type="email"][readonly],
+input[type="number"][readonly],
 textarea[readonly] {
   background-color: #f0f0f0;
 }
 input[type="text"][readonly]:hover,
 input[type="password"][readonly]:hover,
 input[type="email"][readonly]:hover,
+input[type="number"][readonly]:hover,
 textarea[readonly]:hover {
   border-color: #62afdb;
 }
 input[type="text"][readonly]:focus,
 input[type="password"][readonly]:focus,
 input[type="email"][readonly]:focus,
+input[type="number"][readonly]:focus,
 textarea[readonly]:focus {
   border-color: #b6b6b6;
   box-shadow: none;
diff --git a/admin-ui-styles/src/main/resources/META-INF/resources/admin-ui/css/forms.less b/admin-ui-styles/src/main/resources/META-INF/resources/admin-ui/css/forms.less
index c9c8543..8dff0c3 100644
--- a/admin-ui-styles/src/main/resources/META-INF/resources/admin-ui/css/forms.less
+++ b/admin-ui-styles/src/main/resources/META-INF/resources/admin-ui/css/forms.less
@@ -20,6 +20,7 @@ select:-moz-focusring {
 input[type="text"],
 input[type="password"],
 input[type="email"],
+input[type="number"],
 textarea {
     font-size: 1.1em;
     padding: 0 0.545454545454545em;
@@ -79,6 +80,7 @@ textarea {
 input[type="text"][readonly],
 input[type="password"][readonly],
 input[type="email"][readonly],
+input[type="number"][readonly],
 textarea[readonly] {
     background-color: #f0f0f0;
     
diff --git a/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
index c668a70..33c56f6 100755
--- a/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
@@ -32,6 +32,7 @@ public class RealmRepresentation {
     protected Set<String> requiredCredentials;
     protected Set<String> requiredApplicationCredentials;
     protected Set<String> requiredOAuthClientCredentials;
+    protected String passwordPolicy;
     protected List<UserRepresentation> users;
     protected List<UserRoleMappingRepresentation> roleMappings;
     protected List<ScopeMappingRepresentation> scopeMappings;
@@ -199,6 +200,14 @@ public class RealmRepresentation {
         this.requiredOAuthClientCredentials = requiredOAuthClientCredentials;
     }
 
+    public String getPasswordPolicy() {
+        return passwordPolicy;
+    }
+
+    public void setPasswordPolicy(String passwordPolicy) {
+        this.passwordPolicy = passwordPolicy;
+    }
+
     public Integer getAccessCodeLifespan() {
         return accessCodeLifespan;
     }
diff --git a/examples/wildfly-demo/server/pom.xml b/examples/wildfly-demo/server/pom.xml
index fc030e0..ae00bea 100755
--- a/examples/wildfly-demo/server/pom.xml
+++ b/examples/wildfly-demo/server/pom.xml
@@ -107,6 +107,10 @@
             <artifactId>de.flapdoodle.embed.mongo</artifactId>
         </dependency>
         <dependency>
+            <groupId>org.jboss.spec.javax.servlet</groupId>
+            <artifactId>jboss-servlet-api_3.0_spec</artifactId>
+        </dependency>
+        <dependency>
             <groupId>junit</groupId>
             <artifactId>junit</artifactId>
             <version>4.1</version>
diff --git a/forms/src/main/java/org/keycloak/forms/LoginBean.java b/forms/src/main/java/org/keycloak/forms/LoginBean.java
index 02fc10b..4e4184e 100755
--- a/forms/src/main/java/org/keycloak/forms/LoginBean.java
+++ b/forms/src/main/java/org/keycloak/forms/LoginBean.java
@@ -38,8 +38,6 @@ public class LoginBean {
 
     private String password;
 
-    private List<RequiredCredential> requiredCredentials;
-
     public LoginBean(RealmBean realm, MultivaluedMap<String, String> formData){
 
         this.realm = realm;
@@ -48,14 +46,6 @@ public class LoginBean {
             username = formData.getFirst("username");
             password = formData.getFirst("password");
         }
-
-        requiredCredentials = new LinkedList<RequiredCredential>();
-        for (org.keycloak.models.RequiredCredentialModel c : realm.getRealm().getRequiredCredentials()) {
-            if (c.isInput()) {
-                requiredCredentials.add(new RequiredCredential(c.getType(), c.isSecret(), c.getFormLabel()));
-            }
-        }
-
     }
 
     public String getUsername() {
@@ -66,10 +56,6 @@ public class LoginBean {
         return password;
     }
 
-    public List<RequiredCredential> getRequiredCredentials() {
-        return requiredCredentials;
-    }
-
     public RealmBean getRealm() {
         return realm;
     }
diff --git a/forms/src/main/java/org/keycloak/forms/MessageBean.java b/forms/src/main/java/org/keycloak/forms/MessageBean.java
index c1b16a4..c24e66a 100644
--- a/forms/src/main/java/org/keycloak/forms/MessageBean.java
+++ b/forms/src/main/java/org/keycloak/forms/MessageBean.java
@@ -23,6 +23,8 @@ package org.keycloak.forms;
 
 import org.keycloak.services.resources.flows.FormFlows;
 
+import java.util.ResourceBundle;
+
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
  */
@@ -32,13 +34,12 @@ public class MessageBean {
 
     private FormFlows.MessageType type;
 
-    // Message is considered ERROR by default
-    public MessageBean(String summary) {
-        this(summary, FormFlows.MessageType.ERROR);
-    }
-
-    public MessageBean(String summary, FormFlows.MessageType type) {
-        this.summary = summary;
+    public MessageBean(String summary, FormFlows.MessageType type, ResourceBundle rb) {
+        if (rb.containsKey(summary)) {
+            this.summary = rb.getString(summary);
+        } else {
+            this.summary = summary;
+        }
         this.type = type;
     }
 
diff --git a/forms/src/main/java/org/keycloak/service/FormServiceImpl.java b/forms/src/main/java/org/keycloak/service/FormServiceImpl.java
index 329dbf2..596d893 100755
--- a/forms/src/main/java/org/keycloak/service/FormServiceImpl.java
+++ b/forms/src/main/java/org/keycloak/service/FormServiceImpl.java
@@ -79,19 +79,18 @@ public class FormServiceImpl implements FormService {
     }
 
     public String process(String pageId, FormServiceDataBean dataBean){
-
         Map<String, Object> attributes = new HashMap<String, Object>();
 
+        ResourceBundle rb = ResourceBundle.getBundle(BUNDLE);
+        attributes.put("rb", rb);
+
         if (dataBean.getMessage() != null){
-            attributes.put("message", new MessageBean(dataBean.getMessage(), dataBean.getMessageType()));
+            attributes.put("message", new MessageBean(dataBean.getMessage(), dataBean.getMessageType(), rb));
         }
 
         RealmBean realm = new RealmBean(dataBean.getRealm());
         attributes.put("template", new TemplateBean(realm, dataBean.getContextPath()));
 
-        ResourceBundle rb = ResourceBundle.getBundle(BUNDLE);
-        attributes.put("rb", rb);
-
         if (commandMap.containsKey(pageId)){
             commandMap.get(pageId).exec(attributes, dataBean);
         }
diff --git a/forms/src/main/resources/META-INF/resources/forms/theme/default/css/login-register.css b/forms/src/main/resources/META-INF/resources/forms/theme/default/css/login-register.css
index e26aa9d..cf293d4 100644
--- a/forms/src/main/resources/META-INF/resources/forms/theme/default/css/login-register.css
+++ b/forms/src/main/resources/META-INF/resources/forms/theme/default/css/login-register.css
@@ -129,10 +129,10 @@ body {
   margin-bottom: 0.54545454545455em;
   /* 6px */
 }
-.rcue-login-register form > input[type="button"],
-.rcue-login-register form > input[type="submit"]{
-  float: right;
-  margin-top: 0.76923076923077em;
+.rcue-login-register div.form-buttons {
+    display: inline;
+    float: right;
+    margin-top: 0.76923076923077em;
   /* 10px */
 
 }
diff --git a/forms/src/main/resources/META-INF/resources/forms/theme/default/login.ftl b/forms/src/main/resources/META-INF/resources/forms/theme/default/login.ftl
index 2e90a5d..248f17b 100755
--- a/forms/src/main/resources/META-INF/resources/forms/theme/default/login.ftl
+++ b/forms/src/main/resources/META-INF/resources/forms/theme/default/login.ftl
@@ -16,13 +16,14 @@
                 <label for="username">${rb.getString('username')}</label><input id="username" name="username" value="${login.username!''}" type="text" autofocus />
             </div>
 
-            <#list login.requiredCredentials as c>
-                <div>
-                    <label for="${c.name}">${rb.getString(c.label)}</label><input id="${c.name}" name="${c.name}" type="${c.inputType}" />
-                </div>
-            </#list>
+            <div>
+                <label for="password">${rb.getString('password')}</label><input id="password" name="password" type="password" />
+            </div>
 
-            <input class="btn-primary" type="submit" value="Log In"/>
+            <div class="form-buttons">
+                <input class="btn-primary" name="login" type="submit" value="Log In"/>
+                <input class="btn-secondary" name="cancel" type="submit" value="Cancel"/>
+            </div>
 
             <div class="aside-btn">
                 <p>Forgot <a href="${url.loginPasswordResetUrl}">Password</a>?</p>
diff --git a/forms/src/main/resources/META-INF/resources/forms/theme/default/template-login.ftl b/forms/src/main/resources/META-INF/resources/forms/theme/default/template-login.ftl
index 10c668a..c46227b 100644
--- a/forms/src/main/resources/META-INF/resources/forms/theme/default/template-login.ftl
+++ b/forms/src/main/resources/META-INF/resources/forms/theme/default/template-login.ftl
@@ -40,7 +40,7 @@
                     <#if message?has_content && message.error>
                         <div class="feedback error bottom-left show">
                             <p>
-                                <strong id="loginError">${rb.getString(message.summary)}</strong><br/>${rb.getString('emailErrorInfo')}
+                                <strong id="loginError">${message.summary}</strong><br/>${rb.getString('emailErrorInfo')}
                             </p>
                         </div>
                     </#if>
diff --git a/forms/src/main/resources/META-INF/resources/forms/theme/default/template-login-action.ftl b/forms/src/main/resources/META-INF/resources/forms/theme/default/template-login-action.ftl
index f3113dc..977f1cd 100644
--- a/forms/src/main/resources/META-INF/resources/forms/theme/default/template-login-action.ftl
+++ b/forms/src/main/resources/META-INF/resources/forms/theme/default/template-login-action.ftl
@@ -20,7 +20,7 @@
     <div class="feedback-aligner">
         <#if message?has_content && message.warning>
         <div class="feedback warning show">
-            <p><strong>${rb.getString('actionWarningHeader')} ${rb.getString(message.summary)}</strong><br/>${rb.getString('actionFollow')}</p>
+            <p><strong>${rb.getString('actionWarningHeader')} ${message.summary}</strong><br/>${rb.getString('actionFollow')}</p>
         </div>
         </#if>
     </div>
@@ -47,13 +47,13 @@
                         <#if message.error>
                             <div class="feedback error bottom-left show">
                                 <p>
-                                    <strong id="loginError">${rb.getString(message.summary)}</strong><br/>${rb.getString('emailErrorInfo')}
+                                    <strong id="loginError">${message.summary}</strong><br/>${rb.getString('emailErrorInfo')}
                                 </p>
                             </div>
                         <#elseif message.success>
                             <div class="feedback success bottom-left show">
                                 <p>
-                                    <strong>${rb.getString('successHeader')}</strong> ${rb.getString(message.summary)}
+                                    <strong>${rb.getString('successHeader')}</strong> ${message.summary}
                                 </p>
                             </div>
                         </#if>
diff --git a/forms/src/main/resources/META-INF/resources/forms/theme/default/template-main.ftl b/forms/src/main/resources/META-INF/resources/forms/theme/default/template-main.ftl
index 4a45d93..437ceb7 100644
--- a/forms/src/main/resources/META-INF/resources/forms/theme/default/template-main.ftl
+++ b/forms/src/main/resources/META-INF/resources/forms/theme/default/template-main.ftl
@@ -31,10 +31,10 @@
     <#if message?has_content>
     <div class="feedback-aligner">
         <#if message.success>
-        <div class="feedback success show"><p><strong>${rb.getString('successHeader')}</strong> ${rb.getString(message.summary)}</p></div>
+        <div class="feedback success show"><p><strong>${rb.getString('successHeader')}</strong> ${message.summary}</p></div>
         </#if>
         <#if message.error>
-        <div class="feedback error show"><p><strong>${rb.getString('errorHeader')}</strong> ${rb.getString(message.summary)}</p></div>
+        <div class="feedback error show"><p><strong>${rb.getString('errorHeader')}</strong> ${message.summary}</p></div>
         </#if>
     </div>
     </#if>
diff --git a/model/api/pom.xml b/model/api/pom.xml
index 48e4ac9..ee9aee5 100755
--- a/model/api/pom.xml
+++ b/model/api/pom.xml
@@ -13,6 +13,11 @@
     <description/>
 
     <dependencies>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
    </dependencies>
     <build>
         <plugins>
diff --git a/model/api/src/main/java/org/keycloak/models/PasswordPolicy.java b/model/api/src/main/java/org/keycloak/models/PasswordPolicy.java
new file mode 100644
index 0000000..99636b9
--- /dev/null
+++ b/model/api/src/main/java/org/keycloak/models/PasswordPolicy.java
@@ -0,0 +1,182 @@
+package org.keycloak.models;
+
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class PasswordPolicy {
+
+    private List<Policy> policies;
+    private String policyString;
+
+    public PasswordPolicy(String policyString) {
+        if (policyString == null || policyString.length() == 0) {
+            this.policyString = null;
+            policies = Collections.emptyList();
+        } else {
+            this.policyString = policyString;
+            policies = parse(policyString);
+        }
+    }
+
+    private static List<Policy> parse(String policyString) {
+        List<Policy> list = new LinkedList<Policy>();
+        String[] policies = policyString.split(" and ");
+        for (String policy : policies) {
+            policy = policy.trim();
+
+            String name;
+            String[] args = null;
+
+            int i = policy.indexOf('(');
+            if (i == -1) {
+                name = policy.trim();
+            } else {
+                name = policy.substring(0, i).trim();
+                args = policy.substring(i + 1, policy.length() - 1).split(",");
+                for (int j = 0; j < args.length; j++) {
+                    args[j] = args[j].trim();
+                }
+            }
+
+            if (name.equals(Length.NAME)) {
+                list.add(new Length(args));
+            } else if (name.equals(Digits.NAME)) {
+                list.add(new Digits(args));
+            } else if (name.equals(LowerCase.NAME)) {
+                list.add(new LowerCase(args));
+            } else if (name.equals(UpperCase.NAME)) {
+                list.add(new UpperCase(args));
+            } else if (name.equals(SpecialChars.NAME)) {
+                list.add(new SpecialChars(args));
+            }
+        }
+        return list;
+    }
+
+    public String validate(String password) {
+        for (Policy p : policies) {
+            String error = p.validate(password);
+            if (error != null) {
+                return error;
+            }
+        }
+        return null;
+    }
+
+    private static interface Policy {
+        public String validate(String password);
+    }
+
+    private static class Length implements Policy {
+        private static final String NAME = "length";
+        private int min;
+
+        public Length(String[] args) {
+            min = intArg(NAME, 8, args);
+        }
+
+        @Override
+        public String validate(String password) {
+            return password.length() < min ? "Invalid password: minimum length " + min : null;
+        }
+    }
+
+    private static class Digits implements Policy {
+        private static final String NAME = "digits";
+        private int min;
+
+        public Digits(String[] args) {
+            min = intArg(NAME, 1, args);
+        }
+
+        @Override
+        public String validate(String password) {
+            int count = 0;
+            for (char c : password.toCharArray()) {
+                if (Character.isDigit(c)) {
+                    count++;
+                }
+            }
+            return count < min ? "Invalid password: must contain at least " + count + " numerical digits" : null;
+        }
+    }
+
+    private static class LowerCase implements Policy {
+        private static final String NAME = "lowerCase";
+        private int min;
+
+        public LowerCase(String[] args) {
+            min = intArg(NAME, 1, args);
+        }
+
+        @Override
+        public String validate(String password) {
+            int count = 0;
+            for (char c : password.toCharArray()) {
+                if (Character.isLowerCase(c)) {
+                    count++;
+                }
+            }
+            return count < min ? "Invalid password: must contain at least " + count + " lower case characters": null;
+        }
+    }
+
+    private static class UpperCase implements Policy {
+        private static final String NAME = "upperCase";
+        private int min;
+
+        public UpperCase(String[] args) {
+            min = intArg(NAME, 1, args);
+        }
+
+        @Override
+        public String validate(String password) {
+            int count = 0;
+            for (char c : password.toCharArray()) {
+                if (Character.isUpperCase(c)) {
+                    count++;
+                }
+            }
+            return count < min ? "Invalid password: must contain at least " + count + " upper case characters" : null;
+        }
+    }
+
+    private static class SpecialChars implements Policy {
+        private static final String NAME = "specialChars";
+        private int min;
+
+        public SpecialChars(String[] args) {
+            min = intArg(NAME, 1, args);
+        }
+
+        @Override
+        public String validate(String password) {
+            int count = 0;
+            for (char c : password.toCharArray()) {
+                if (!Character.isLetterOrDigit(c)) {
+                    count++;
+                }
+            }
+            return count < min ? "Invalid password: must contain at least " + count + " special characters" : null;
+        }
+    }
+
+    private static int intArg(String policy, int defaultValue, String... args) {
+        if (args == null || args.length == 0) {
+            return defaultValue;
+        } else if (args.length == 1) {
+            return Integer.parseInt(args[0]);
+        } else {
+            throw new IllegalArgumentException("Invalid arguments to " + policy + ", expect no argument or single integer");
+        }
+    }
+
+    @Override
+    public String toString() {
+        return policyString;
+    }
+}
diff --git a/model/api/src/main/java/org/keycloak/models/RealmModel.java b/model/api/src/main/java/org/keycloak/models/RealmModel.java
index d4af49f..a4527ae 100755
--- a/model/api/src/main/java/org/keycloak/models/RealmModel.java
+++ b/model/api/src/main/java/org/keycloak/models/RealmModel.java
@@ -74,6 +74,10 @@ public interface RealmModel extends RoleContainerModel, RoleMapperModel, ScopeMa
 
     void addRequiredCredential(String cred);
 
+    PasswordPolicy getPasswordPolicy();
+
+    void setPasswordPolicy(PasswordPolicy policy);
+
     boolean validatePassword(UserModel user, String password);
 
     boolean validateTOTP(UserModel user, String password, String token);
diff --git a/model/api/src/main/test/java/org/keycloak/models/PasswordPolicyTest.java b/model/api/src/main/test/java/org/keycloak/models/PasswordPolicyTest.java
new file mode 100644
index 0000000..3fd7271
--- /dev/null
+++ b/model/api/src/main/test/java/org/keycloak/models/PasswordPolicyTest.java
@@ -0,0 +1,78 @@
+package org.keycloak.models;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class PasswordPolicyTest {
+
+    @Test
+    public void testLength() {
+        PasswordPolicy policy = new PasswordPolicy("length");
+        Assert.assertNotNull(policy.validate("1234567"));
+        Assert.assertNull(policy.validate("12345678"));
+
+        policy = new PasswordPolicy("length(4)");
+        Assert.assertNotNull(policy.validate("123"));
+        Assert.assertNull(policy.validate("1234"));
+    }
+
+    @Test
+    public void testDigits() {
+        PasswordPolicy policy = new PasswordPolicy("digits");
+        Assert.assertNotNull(policy.validate("abcd"));
+        Assert.assertNull(policy.validate("abcd1"));
+
+        policy = new PasswordPolicy("digits(2)");
+        Assert.assertNotNull(policy.validate("abcd1"));
+        Assert.assertNull(policy.validate("abcd12"));
+    }
+
+    @Test
+    public void testLowerCase() {
+        PasswordPolicy policy = new PasswordPolicy("lowerCase");
+        Assert.assertNotNull(policy.validate("ABCD1234"));
+        Assert.assertNull(policy.validate("ABcD1234"));
+
+        policy = new PasswordPolicy("lowerCase(2)");
+        Assert.assertNotNull(policy.validate("ABcD1234"));
+        Assert.assertNull(policy.validate("aBcD1234"));
+    }
+
+    @Test
+    public void testUpperCase() {
+        PasswordPolicy policy = new PasswordPolicy("upperCase");
+        Assert.assertNotNull(policy.validate("abcd1234"));
+        Assert.assertNull(policy.validate("abCd1234"));
+
+        policy = new PasswordPolicy("upperCase(2)");
+        Assert.assertNotNull(policy.validate("abCd1234"));
+        Assert.assertNull(policy.validate("AbCd1234"));
+    }
+
+    @Test
+    public void testSpecialChars() {
+        PasswordPolicy policy = new PasswordPolicy("specialChars");
+        Assert.assertNotNull(policy.validate("abcd1234"));
+        Assert.assertNull(policy.validate("ab&d1234"));
+
+        policy = new PasswordPolicy("specialChars(2)");
+        Assert.assertNotNull(policy.validate("ab&d1234"));
+        Assert.assertNull(policy.validate("ab&d-234"));
+    }
+
+    @Test
+    public void testComplex() {
+        PasswordPolicy policy = new PasswordPolicy("length(8) and digits(2) and lowerCase(2) and upperCase(2) and specialChars(2)");
+        Assert.assertNotNull(policy.validate("12aaBB&"));
+        Assert.assertNotNull(policy.validate("aaaaBB&-"));
+        Assert.assertNotNull(policy.validate("12AABB&-"));
+        Assert.assertNotNull(policy.validate("12aabb&-"));
+        Assert.assertNotNull(policy.validate("12aaBBcc"));
+
+        Assert.assertNull(policy.validate("12aaBB&-"));
+    }
+
+}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java
index d82c68e..5a59a4c 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java
@@ -34,6 +34,7 @@ public class RealmEntity {
     protected boolean resetPasswordAllowed;
     protected boolean social;
     protected boolean automaticRegistrationAfterSocialLogin;
+    protected String passwordPolicy;
 
     protected int tokenLifespan;
     protected int accessCodeLifespan;
@@ -269,4 +270,13 @@ public class RealmEntity {
     public void setDefaultRoles(Collection<RoleEntity> defaultRoles) {
         this.defaultRoles = defaultRoles;
     }
+
+    public String getPasswordPolicy() {
+        return passwordPolicy;
+    }
+
+    public void setPasswordPolicy(String passwordPolicy) {
+        this.passwordPolicy = passwordPolicy;
+    }
 }
+
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 7a47448..6e6c8f1 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
@@ -4,6 +4,7 @@ import org.bouncycastle.openssl.PEMWriter;
 import org.keycloak.PemUtils;
 import org.keycloak.models.ApplicationModel;
 import org.keycloak.models.OAuthClientModel;
+import org.keycloak.models.PasswordPolicy;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.RequiredCredentialModel;
 import org.keycloak.models.RoleModel;
@@ -37,6 +38,7 @@ public class RealmAdapter implements RealmModel {
     protected EntityManager em;
     protected volatile transient PublicKey publicKey;
     protected volatile transient PrivateKey privateKey;
+    private PasswordPolicy passwordPolicy;
 
     public RealmAdapter(EntityManager em, RealmEntity realm) {
         this.em = em;
@@ -1037,4 +1039,18 @@ public class RealmAdapter implements RealmModel {
         em.flush();
     }
 
+    @Override
+    public PasswordPolicy getPasswordPolicy() {
+        if (passwordPolicy == null) {
+            passwordPolicy = new PasswordPolicy(realm.getPasswordPolicy());
+        }
+        return passwordPolicy;
+    }
+
+    @Override
+    public void setPasswordPolicy(PasswordPolicy policy) {
+        this.passwordPolicy = policy;
+        realm.setPasswordPolicy(policy.toString());
+        em.flush();
+    }
 }
diff --git a/model/picketlink/src/main/java/org/keycloak/models/picketlink/mappings/RealmData.java b/model/picketlink/src/main/java/org/keycloak/models/picketlink/mappings/RealmData.java
index 06e0736..ad1fbe5 100755
--- a/model/picketlink/src/main/java/org/keycloak/models/picketlink/mappings/RealmData.java
+++ b/model/picketlink/src/main/java/org/keycloak/models/picketlink/mappings/RealmData.java
@@ -27,6 +27,7 @@ public class RealmData extends AbstractPartition {
     private String[] defaultRoles;
     private Map<String, String> smtpConfig;
     private Map<String, String> socialConfig;
+    private String passwordPolicy;
 
     public RealmData() {
         super(null);
@@ -185,4 +186,13 @@ public class RealmData extends AbstractPartition {
     public void setSocialConfig(Map<String, String> socialConfig) {
         this.socialConfig = socialConfig;
     }
+
+    @AttributeProperty
+    public String getPasswordPolicy() {
+        return passwordPolicy;
+    }
+
+    public void setPasswordPolicy(String passwordPolicy) {
+        this.passwordPolicy = passwordPolicy;
+    }
 }
diff --git a/model/picketlink/src/main/java/org/keycloak/models/picketlink/RealmAdapter.java b/model/picketlink/src/main/java/org/keycloak/models/picketlink/RealmAdapter.java
index 1300b53..798643e 100755
--- a/model/picketlink/src/main/java/org/keycloak/models/picketlink/RealmAdapter.java
+++ b/model/picketlink/src/main/java/org/keycloak/models/picketlink/RealmAdapter.java
@@ -6,6 +6,7 @@ import org.keycloak.models.ApplicationModel;
 import org.keycloak.models.IdGenerator;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.OAuthClientModel;
+import org.keycloak.models.PasswordPolicy;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.RequiredCredentialModel;
 import org.keycloak.models.RoleModel;
@@ -62,6 +63,7 @@ public class RealmAdapter implements RealmModel {
     protected PartitionManager partitionManager;
     protected RelationshipManager relationshipManager;
     protected KeycloakSession session;
+    private PasswordPolicy passwordPolicy;
 
     public RealmAdapter(KeycloakSession session, RealmData realm, PartitionManager partitionManager) {
         this.session = session;
@@ -977,4 +979,19 @@ public class RealmAdapter implements RealmModel {
         realm.setSocialConfig(socialConfig);
         updateRealm();
     }
+
+    @Override
+    public PasswordPolicy getPasswordPolicy() {
+        if (passwordPolicy == null) {
+            passwordPolicy = new PasswordPolicy(realm.getPasswordPolicy());
+        }
+        return passwordPolicy;
+    }
+
+    @Override
+    public void setPasswordPolicy(PasswordPolicy policy) {
+        this.passwordPolicy = policy;
+        realm.setPasswordPolicy(policy.toString());
+        updateRealm();
+    }
 }
diff --git a/services/src/main/java/org/keycloak/services/managers/RealmManager.java b/services/src/main/java/org/keycloak/services/managers/RealmManager.java
index 446e86d..08b6b3d 100755
--- a/services/src/main/java/org/keycloak/services/managers/RealmManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/RealmManager.java
@@ -5,6 +5,7 @@ import org.keycloak.models.ApplicationModel;
 import org.keycloak.models.Constants;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.OAuthClientModel;
+import org.keycloak.models.PasswordPolicy;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.RequiredCredentialModel;
 import org.keycloak.models.RoleModel;
@@ -64,7 +65,7 @@ public class RealmManager {
     }
 
     public RealmModel createRealm(String name) {
-        return createRealm(generateId(), name);
+        return createRealm(name, name);
     }
 
     public RealmModel createRealm(String id, String name) {
@@ -110,6 +111,9 @@ public class RealmManager {
         if (rep.getRequiredApplicationCredentials() != null) {
             realm.updateRequiredApplicationCredentials(rep.getRequiredApplicationCredentials());
         }
+
+        realm.setPasswordPolicy(new PasswordPolicy(rep.getPasswordPolicy()));
+
         if (rep.getDefaultRoles() != null) {
             realm.updateDefaultRoles(rep.getDefaultRoles().toArray(new String[rep.getDefaultRoles().size()]));
         }
@@ -222,6 +226,8 @@ public class RealmManager {
             addOAuthClientRequiredCredential(newRealm, CredentialRepresentation.PASSWORD);
         }
 
+        newRealm.setPasswordPolicy(new PasswordPolicy(rep.getPasswordPolicy()));
+
         if (rep.getUsers() != null) {
             for (UserRepresentation userRep : rep.getUsers()) {
                 UserModel user = createUser(newRealm, userRep);
@@ -473,6 +479,9 @@ public class RealmManager {
         rep.setAccessCodeLifespanUserAction(realm.getAccessCodeLifespanUserAction());
         rep.setSmtpServer(realm.getSmtpConfig());
         rep.setSocialProviders(realm.getSocialConfig());
+        if (realm.getPasswordPolicy() != null) {
+            rep.setPasswordPolicy(realm.getPasswordPolicy().toString());
+        }
 
         ApplicationModel accountManagementApplication = realm.getApplicationNameMap().get(Constants.ACCOUNT_APPLICATION);
         rep.setAccountManagement(accountManagementApplication != null && accountManagementApplication.isEnabled());
diff --git a/services/src/main/java/org/keycloak/services/resources/AccountService.java b/services/src/main/java/org/keycloak/services/resources/AccountService.java
index 22e7269..a62a064 100755
--- a/services/src/main/java/org/keycloak/services/resources/AccountService.java
+++ b/services/src/main/java/org/keycloak/services/resources/AccountService.java
@@ -255,6 +255,11 @@ public class AccountService {
             return forms.setError(Messages.INVALID_PASSWORD_EXISTING).forwardToPassword();
         }
 
+        String error = Validation.validatePassword(formData, realm.getPasswordPolicy());
+        if (error != null) {
+            return forms.setError(error).forwardToPassword();
+        }
+
         UserCredentialModel credentials = new UserCredentialModel();
         credentials.setType(CredentialRepresentation.PASSWORD);
         credentials.setValue(passwordNew);
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ApplicationsResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ApplicationsResource.java
index 29cc957..efcc0f6 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/ApplicationsResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/ApplicationsResource.java
@@ -8,6 +8,7 @@ import org.keycloak.models.RealmModel;
 import org.keycloak.representations.idm.ApplicationRepresentation;
 import org.keycloak.services.managers.ApplicationManager;
 import org.keycloak.services.managers.RealmManager;
+import org.keycloak.services.resources.flows.Flows;
 
 import javax.ws.rs.Consumes;
 import javax.ws.rs.GET;
@@ -58,6 +59,9 @@ public class ApplicationsResource {
     @POST
     @Consumes(MediaType.APPLICATION_JSON)
     public Response createApplication(final @Context UriInfo uriInfo, final ApplicationRepresentation rep) {
+        if (realm.getApplicationNameMap().containsKey(rep.getName())) {
+            return Flows.errors().exists("Application " + rep.getName() + " already exists");
+        }
         ApplicationManager resourceManager = new ApplicationManager(new RealmManager(session));
         ApplicationModel applicationModel = resourceManager.createApplication(realm, rep);
         return Response.created(uriInfo.getAbsolutePathBuilder().path(applicationModel.getId()).build()).build();
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/RealmsAdminResource.java b/services/src/main/java/org/keycloak/services/resources/admin/RealmsAdminResource.java
index 3e2049d..5037906 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/RealmsAdminResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/RealmsAdminResource.java
@@ -8,6 +8,7 @@ import org.keycloak.models.UserModel;
 import org.keycloak.representations.idm.RealmRepresentation;
 import org.keycloak.services.managers.RealmManager;
 import org.keycloak.services.resources.SaasService;
+import org.keycloak.services.resources.flows.Flows;
 
 import javax.ws.rs.*;
 import javax.ws.rs.container.ResourceContext;
@@ -71,6 +72,10 @@ public class RealmsAdminResource {
     public Response importRealm(@Context final UriInfo uriInfo, final RealmRepresentation rep) {
         logger.debug("importRealm: {0}", rep.getRealm());
         RealmManager realmManager = new RealmManager(session);
+        if (realmManager.getRealm(rep.getRealm()) != null) {
+            return Flows.errors().exists("Realm " + rep.getRealm() + " already exists");
+        }
+
         RealmModel realm = realmManager.importRealm(rep, admin);
         URI location = realmUrl(uriInfo).build(realm.getId());
         logger.debug("imported realm success, sending back: {0}", location.toString());
diff --git a/services/src/main/java/org/keycloak/services/resources/flows/OAuthFlows.java b/services/src/main/java/org/keycloak/services/resources/flows/OAuthFlows.java
index b178df9..b69cea8 100755
--- a/services/src/main/java/org/keycloak/services/resources/flows/OAuthFlows.java
+++ b/services/src/main/java/org/keycloak/services/resources/flows/OAuthFlows.java
@@ -67,12 +67,6 @@ public class OAuthFlows {
     }
 
     public Response redirectAccessCode(AccessCodeEntry accessCode, String state, String redirect) {
-        UserModel client = realm.getUser(accessCode.getClient().getLoginName());
-        Set<String> redirectUris = client.getRedirectUris();
-        if (!redirectUris.isEmpty() && !redirectUris.contains(redirect)) {
-            return forwardToSecurityFailure("Invalid redirect_uri " + redirect);
-        }
-
         String code = accessCode.getCode();
         UriBuilder redirectUri = UriBuilder.fromUri(redirect).queryParam("code", code);
         log.debug("redirectAccessCode: state: {0}", state);
@@ -86,11 +80,6 @@ public class OAuthFlows {
     }
 
     public Response redirectError(UserModel client, String error, String state, String redirect) {
-        Set<String> redirectUris = client.getRedirectUris();
-        if (!redirectUris.isEmpty() && !redirectUris.contains(redirect)) {
-            return forwardToSecurityFailure("Invalid redirect_uri " + redirect);
-        }
-
         UriBuilder redirectUri = UriBuilder.fromUri(redirect).queryParam("error", error);
         if (state != null) {
             redirectUri.queryParam("state", state);
diff --git a/services/src/main/java/org/keycloak/services/resources/TokenService.java b/services/src/main/java/org/keycloak/services/resources/TokenService.java
index 580473b..b40e10e 100755
--- a/services/src/main/java/org/keycloak/services/resources/TokenService.java
+++ b/services/src/main/java/org/keycloak/services/resources/TokenService.java
@@ -183,7 +183,7 @@ public class TokenService {
     @POST
     @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
     public Response processLogin(@QueryParam("client_id") final String clientId, @QueryParam("scope") final String scopeParam,
-            @QueryParam("state") final String state, @QueryParam("redirect_uri") final String redirect,
+            @QueryParam("state") final String state, @QueryParam("redirect_uri") String redirect,
             final MultivaluedMap<String, String> formData) {
         logger.debug("TokenService.processLogin");
         OAuthFlows oauth = Flows.oauth(realm, request, uriInfo, authManager, tokenManager);
@@ -199,6 +199,15 @@ public class TokenService {
             return oauth.forwardToSecurityFailure("Login requester not enabled.");
         }
 
+        redirect = verifyRedirectUri(redirect, client);
+        if (redirect == null) {
+            return oauth.forwardToSecurityFailure("Invalid redirect_uri.");
+        }
+
+        if (formData.containsKey("cancel")) {
+            return oauth.redirectError(client, "access_denied", state, redirect);
+        }
+
         String username = formData.getFirst("username");
         UserModel user = realm.getUser(username);
 
@@ -286,6 +295,11 @@ public class TokenService {
             return oauth.forwardToSecurityFailure("Login requester not enabled.");
         }
 
+        redirect = verifyRedirectUri(redirect, client);
+        if (redirect == null) {
+            return oauth.forwardToSecurityFailure("Invalid redirect_uri.");
+        }
+
         if (!realm.isRegistrationAllowed()) {
             logger.warn("Registration not allowed");
             return oauth.forwardToSecurityFailure("Registration not allowed");
@@ -297,6 +311,10 @@ public class TokenService {
         }
 
         String error = Validation.validateRegistrationForm(formData, requiredCredentialTypes);
+        if (error == null) {
+            error = Validation.validatePassword(formData, realm.getPasswordPolicy());
+        }
+
         if (error != null) {
             return Flows.forms(realm, request, uriInfo).setError(error).setFormData(formData)
                     .setSocialRegistration(isSocialRegistration).forwardToRegistration();
@@ -464,7 +482,7 @@ public class TokenService {
     @Path("login")
     @GET
     public Response loginPage(final @QueryParam("response_type") String responseType,
-            final @QueryParam("redirect_uri") String redirect, final @QueryParam("client_id") String clientId,
+            @QueryParam("redirect_uri") String redirect, final @QueryParam("client_id") String clientId,
             final @QueryParam("scope") String scopeParam, final @QueryParam("state") String state, final @QueryParam("prompt") String prompt) {
         logger.info("TokenService.loginPage");
         OAuthFlows oauth = Flows.oauth(realm, request, uriInfo, authManager, tokenManager);
@@ -489,6 +507,11 @@ public class TokenService {
             session.close();
             return null;
         }
+        redirect = verifyRedirectUri(redirect, client);
+        if (redirect == null) {
+            return oauth.forwardToSecurityFailure("Invalid redirect_uri.");
+        }
+
         logger.info("Checking roles...");
         RoleModel resourceRole = realm.getRole(Constants.APPLICATION_ROLE);
         RoleModel identityRequestRole = realm.getRole(Constants.IDENTITY_REQUESTER_ROLE);
@@ -517,7 +540,7 @@ public class TokenService {
     @Path("registrations")
     @GET
     public Response registerPage(final @QueryParam("response_type") String responseType,
-            final @QueryParam("redirect_uri") String redirect, final @QueryParam("client_id") String clientId,
+            @QueryParam("redirect_uri") String redirect, final @QueryParam("client_id") String clientId,
             final @QueryParam("scope") String scopeParam, final @QueryParam("state") String state) {
         logger.info("**********registerPage()");
         OAuthFlows oauth = Flows.oauth(realm, request, uriInfo, authManager, tokenManager);
@@ -537,6 +560,11 @@ public class TokenService {
             return oauth.forwardToSecurityFailure("Login requester not enabled.");
         }
 
+        redirect = verifyRedirectUri(redirect, client);
+        if (redirect == null) {
+            return oauth.forwardToSecurityFailure("Invalid redirect_uri.");
+        }
+
         if (!realm.isRegistrationAllowed()) {
             logger.warn("Registration not allowed");
             return oauth.forwardToSecurityFailure("Registration not allowed");
@@ -605,4 +633,15 @@ public class TokenService {
         return location.build();
     }
 
+    protected String verifyRedirectUri(String redirectUri, UserModel client) {
+        if (redirectUri == null) {
+            return client.getRedirectUris().size() == 1 ? client.getRedirectUris().iterator().next() : null;
+        } else if (client.getRedirectUris().isEmpty()) {
+            return redirectUri;
+        } else {
+            String r = redirectUri.indexOf('?') != -1 ? redirectUri.substring(0, redirectUri.indexOf('?')) : redirectUri;
+            return client.getRedirectUris().contains(r) ? redirectUri : null;
+        }
+    }
+
 }
diff --git a/services/src/main/java/org/keycloak/services/validation/Validation.java b/services/src/main/java/org/keycloak/services/validation/Validation.java
index c652849..a840d08 100755
--- a/services/src/main/java/org/keycloak/services/validation/Validation.java
+++ b/services/src/main/java/org/keycloak/services/validation/Validation.java
@@ -1,5 +1,6 @@
 package org.keycloak.services.validation;
 
+import org.keycloak.models.PasswordPolicy;
 import org.keycloak.representations.idm.CredentialRepresentation;
 import org.keycloak.services.messages.Messages;
 
@@ -38,6 +39,10 @@ public class Validation {
         return null;
     }
 
+    public static String validatePassword(MultivaluedMap<String, String> formData, PasswordPolicy policy) {
+        return policy.validate(formData.getFirst("password"));
+    }
+
     public static String validateUpdateProfileForm(MultivaluedMap<String, String> formData) {
         if (isEmpty(formData.getFirst("firstName"))) {
             return Messages.MISSING_FIRST_NAME;
diff --git a/services/src/test/java/org/keycloak/test/ModelTest.java b/services/src/test/java/org/keycloak/test/ModelTest.java
index 6771e29..5f0728a 100755
--- a/services/src/test/java/org/keycloak/test/ModelTest.java
+++ b/services/src/test/java/org/keycloak/test/ModelTest.java
@@ -6,6 +6,7 @@ import org.junit.Before;
 import org.junit.Test;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.models.PasswordPolicy;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.RoleModel;
 import org.keycloak.representations.idm.RealmRepresentation;
@@ -46,6 +47,7 @@ public class ModelTest extends AbstractKeycloakServerTest {
         realm.setSslNotRequired(true);
         realm.setVerifyEmail(true);
         realm.setTokenLifespan(1000);
+        realm.setPasswordPolicy(new PasswordPolicy("length"));
         realm.setAccessCodeLifespan(1001);
         realm.setAccessCodeLifespanUserAction(1002);
         realm.setPublicKeyPem("0234234");
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionTotpSetupTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionTotpSetupTest.java
index 4829158..bd5d700 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionTotpSetupTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionTotpSetupTest.java
@@ -35,6 +35,7 @@ import org.keycloak.testsuite.pages.AppPage;
 import org.keycloak.testsuite.pages.AppPage.RequestType;
 import org.keycloak.testsuite.pages.LoginConfigTotpPage;
 import org.keycloak.testsuite.pages.LoginPage;
+import org.keycloak.testsuite.pages.LoginTotpPage;
 import org.keycloak.testsuite.pages.RegisterPage;
 import org.keycloak.testsuite.rule.KeycloakRule;
 import org.keycloak.testsuite.rule.KeycloakRule.KeycloakSetup;
@@ -71,6 +72,9 @@ public class RequiredActionTotpSetupTest {
     protected LoginPage loginPage;
 
     @WebResource
+    protected LoginTotpPage loginTotpPage;
+
+    @WebResource
     protected LoginConfigTotpPage totpPage;
 
     @WebResource
@@ -113,7 +117,8 @@ public class RequiredActionTotpSetupTest {
         oauth.openLogout();
 
         loginPage.open();
-        loginPage.loginTotp("test-user@localhost", "password", totp.generate(totpSecret));
+        loginPage.login("test-user@localhost", "password");
+        loginTotpPage.login(totp.generate(totpSecret));
 
         Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
     }
@@ -146,7 +151,7 @@ public class RequiredActionTotpSetupTest {
         Assert.assertFalse(totpPage.isCurrent());
 
         // Login with one-time password
-        loginPage.loginTotp("setupTotp2", "password2", totp.generate(totpCode));
+        loginTotpPage.login(totp.generate(totpCode));
 
         // Open account page
         accountTotpPage.open();
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/AccountTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/AccountTest.java
index 17d6348..020a838 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/AccountTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/AccountTest.java
@@ -164,6 +164,36 @@ public class AccountTest {
     }
 
     @Test
+    public void changePasswordWithPasswordPolicy() {
+        keycloakRule.configure(new KeycloakRule.KeycloakSetup() {
+            @Override
+            public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+                appRealm.setPasswordPolicy(new PasswordPolicy("length"));
+            }
+        });
+
+        try {
+            changePasswordPage.open();
+            loginPage.login("test-user@localhost", "password");
+
+            changePasswordPage.changePassword("", "new", "new");
+
+            Assert.assertTrue(profilePage.isError());
+
+            changePasswordPage.changePassword("password", "new-password", "new-password");
+
+            Assert.assertTrue(profilePage.isSuccess());
+        } finally {
+            keycloakRule.configure(new KeycloakRule.KeycloakSetup() {
+                @Override
+                public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+                    appRealm.setPasswordPolicy(new PasswordPolicy(null));
+                }
+            });
+        }
+    }
+
+    @Test
     public void changeProfile() {
         profilePage.open();
         loginPage.login("test-user@localhost", "password");
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 fab49b2..23750c1 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LoginTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LoginTest.java
@@ -25,6 +25,7 @@ import org.junit.Assert;
 import org.junit.ClassRule;
 import org.junit.Rule;
 import org.junit.Test;
+import org.keycloak.testsuite.OAuthClient;
 import org.keycloak.testsuite.pages.AppPage;
 import org.keycloak.testsuite.pages.AppPage.RequestType;
 import org.keycloak.testsuite.pages.LoginPage;
@@ -45,6 +46,10 @@ public class LoginTest {
     public WebRule webRule = new WebRule(this);
 
     @WebResource
+    protected OAuthClient oauth;
+
+
+    @WebResource
     protected WebDriver driver;
 
     @WebResource
@@ -79,6 +84,17 @@ public class LoginTest {
         loginPage.login("test-user@localhost", "password");
         
         Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
+        Assert.assertNotNull(oauth.getCurrentQuery().get("code"));
+    }
+
+    @Test
+    public void loginCancel() {
+        loginPage.open();
+        loginPage.cancel();
+
+        Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
+
+        Assert.assertEquals("access_denied", oauth.getCurrentQuery().get("error"));
     }
 
 }
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/RegisterTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/RegisterTest.java
index e82b31f..cd7795e 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/RegisterTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/RegisterTest.java
@@ -25,6 +25,9 @@ import org.junit.Assert;
 import org.junit.ClassRule;
 import org.junit.Rule;
 import org.junit.Test;
+import org.keycloak.models.PasswordPolicy;
+import org.keycloak.models.RealmModel;
+import org.keycloak.services.managers.RealmManager;
 import org.keycloak.testsuite.pages.AppPage;
 import org.keycloak.testsuite.pages.AppPage.RequestType;
 import org.keycloak.testsuite.pages.LoginPage;
@@ -94,6 +97,37 @@ public class RegisterTest {
     }
 
     @Test
+    public void registerPasswordPolicy() {
+        keycloakRule.configure(new KeycloakRule.KeycloakSetup() {
+            @Override
+            public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+                appRealm.setPasswordPolicy(new PasswordPolicy("length"));
+            }
+        });
+
+        try {
+            loginPage.open();
+            loginPage.clickRegister();
+            registerPage.assertCurrent();
+
+            registerPage.register("firstName", "lastName", "email", "registerPasswordPolicy", "pass", "pass");
+
+            registerPage.assertCurrent();
+            Assert.assertEquals("Invalid password: minimum length 8", registerPage.getError());
+
+            registerPage.register("firstName", "lastName", "email", "registerPasswordPolicy", "password", "password");
+            Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
+        } finally {
+            keycloakRule.configure(new KeycloakRule.KeycloakSetup() {
+                @Override
+                public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+                    appRealm.setPasswordPolicy(new PasswordPolicy(null));
+                }
+            });
+        }
+    }
+
+    @Test
     public void registerUserMissingUsername() {
         loginPage.open();
         loginPage.clickRegister();
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/AuthorizationCodeTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/AuthorizationCodeTest.java
index 017aca5..f358882 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/AuthorizationCodeTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/AuthorizationCodeTest.java
@@ -98,31 +98,6 @@ public class AuthorizationCodeTest {
     }
 
     @Test
-    public void authorizationRequestInvalidRedirectUri() throws IOException {
-        keycloakRule.configure(new KeycloakRule.KeycloakSetup() {
-            @Override
-            public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
-                for (ApplicationModel app : appRealm.getApplications()) {
-                    if (app.getName().equals("test-app")) {
-                        UserModel client = app.getApplicationUser();
-                        client.addRedirectUri(oauth.getRedirectUri());
-                    }
-                }
-            }
-        });
-
-        oauth.redirectUri("http://invalid");
-        oauth.state("mystate");
-
-        AuthorizationCodeResponse response = oauth.doLogin("test-user@localhost", "password");
-
-        Assert.assertFalse(response.isRedirected());
-
-        Assert.assertTrue(errorPage.isCurrent());
-        Assert.assertEquals("Invalid redirect_uri http://invalid", errorPage.getError());
-    }
-
-    @Test
     public void authorizationRequestNoState() throws IOException {
         AuthorizationCodeResponse response = oauth.doLogin("test-user@localhost", "password");
 
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/OAuthRedirectUriTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/OAuthRedirectUriTest.java
new file mode 100755
index 0000000..5762c9d
--- /dev/null
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/OAuthRedirectUriTest.java
@@ -0,0 +1,174 @@
+/*
+ * 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.oauth;
+
+import org.junit.Assert;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.keycloak.models.ApplicationModel;
+import org.keycloak.models.RealmModel;
+import org.keycloak.representations.SkeletonKeyToken;
+import org.keycloak.services.managers.RealmManager;
+import org.keycloak.testsuite.OAuthClient;
+import org.keycloak.testsuite.pages.ErrorPage;
+import org.keycloak.testsuite.pages.LoginPage;
+import org.keycloak.testsuite.pages.OAuthGrantPage;
+import org.keycloak.testsuite.rule.KeycloakRule;
+import org.keycloak.testsuite.rule.WebResource;
+import org.keycloak.testsuite.rule.WebRule;
+import org.openqa.selenium.WebDriver;
+
+import java.io.IOException;
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:vrockai@redhat.com">Viliam Rockai</a>
+ */
+public class OAuthRedirectUriTest {
+
+    @ClassRule
+    public static KeycloakRule keycloakRule = new KeycloakRule(new KeycloakRule.KeycloakSetup() {
+        @Override
+        public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+            ApplicationModel app = appRealm.getApplicationNameMap().get("test-app");
+            app.getApplicationUser().addRedirectUri("http://localhost:8081/app");
+        }
+    });
+
+    @Rule
+    public WebRule webRule = new WebRule(this);
+
+    @WebResource
+    protected WebDriver driver;
+
+    @WebResource
+    protected OAuthClient oauth;
+
+    @WebResource
+    protected LoginPage loginPage;
+
+    @WebResource
+    protected ErrorPage errorPage;
+
+    @Test
+    public void testNoParam() throws IOException {
+        oauth.redirectUri(null);
+        OAuthClient.AuthorizationCodeResponse response = oauth.doLogin("test-user@localhost", "password");
+
+        Assert.assertNotNull(response.getCode());
+        Assert.assertEquals(oauth.getCurrentRequest(), "http://localhost:8081/app");
+    }
+
+    @Test
+    public void testNoParamMultipleValidUris() throws IOException {
+        keycloakRule.configure(new KeycloakRule.KeycloakSetup() {
+            @Override
+            public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+                appRealm.getApplicationNameMap().get("test-app").getApplicationUser().addRedirectUri("http://localhost:8081/app2");
+            }
+        });
+
+        try {
+            oauth.redirectUri(null);
+            oauth.openLoginForm();
+
+            Assert.assertTrue(errorPage.isCurrent());
+            Assert.assertEquals("Invalid redirect_uri.", errorPage.getError());
+        } finally {
+            keycloakRule.configure(new KeycloakRule.KeycloakSetup() {
+                @Override
+                public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+                    appRealm.getApplicationNameMap().get("test-app").getApplicationUser().removeRedirectUri("http://localhost:8081/app2");
+                }
+            });
+        }
+    }
+
+    @Test
+    public void testNoParamNoValidUris() throws IOException {
+        keycloakRule.configure(new KeycloakRule.KeycloakSetup() {
+            @Override
+            public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+                appRealm.getApplicationNameMap().get("test-app").getApplicationUser().removeRedirectUri("http://localhost:8081/app");
+            }
+        });
+
+        try {
+            oauth.redirectUri(null);
+            oauth.openLoginForm();
+
+            Assert.assertTrue(errorPage.isCurrent());
+            Assert.assertEquals("Invalid redirect_uri.", errorPage.getError());
+        } finally {
+            keycloakRule.configure(new KeycloakRule.KeycloakSetup() {
+                @Override
+                public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+                    appRealm.getApplicationNameMap().get("test-app").getApplicationUser().addRedirectUri("http://localhost:8081/app");
+                }
+            });
+        }
+    }
+
+    @Test
+    public void testNoValidUris() throws IOException {
+        keycloakRule.configure(new KeycloakRule.KeycloakSetup() {
+            @Override
+            public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+                appRealm.getApplicationNameMap().get("test-app").getApplicationUser().removeRedirectUri("http://localhost:8081/app");
+            }
+        });
+
+        try {
+            OAuthClient.AuthorizationCodeResponse response = oauth.doLogin("test-user@localhost", "password");
+
+            Assert.assertNotNull(response.getCode());
+            Assert.assertEquals(oauth.getCurrentRequest(), "http://localhost:8081/app/auth");
+        } finally {
+            keycloakRule.configure(new KeycloakRule.KeycloakSetup() {
+                @Override
+                public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+                    appRealm.getApplicationNameMap().get("test-app").getApplicationUser().addRedirectUri("http://localhost:8081/app");
+                }
+            });
+        }
+    }
+
+    @Test
+    public void testInvalid() throws IOException {
+        oauth.redirectUri("http://localhost:8081/app2");
+        oauth.openLoginForm();
+
+        Assert.assertTrue(errorPage.isCurrent());
+        Assert.assertEquals("Invalid redirect_uri.", errorPage.getError());
+    }
+
+    @Test
+    public void testWithParams() throws IOException {
+        oauth.redirectUri("http://localhost:8081/app?key=value");
+        OAuthClient.AuthorizationCodeResponse response = oauth.doLogin("test-user@localhost", "password");
+
+        Assert.assertNotNull(response.getCode());
+        Assert.assertTrue(driver.getCurrentUrl().startsWith("http://localhost:8081/app?key=value&code="));
+    }
+
+}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/OAuthClient.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/OAuthClient.java
index 5b1118f..eab1260 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/OAuthClient.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/OAuthClient.java
@@ -93,7 +93,7 @@ public class OAuthClient {
 
         driver.findElement(By.id("username")).sendKeys(username);
         driver.findElement(By.id("password")).sendKeys(password);
-        driver.findElement(By.cssSelector("input[type=\"submit\"]")).click();
+        driver.findElement(By.name("login")).click();
 
         return new AuthorizationCodeResponse(this);
     }
@@ -103,7 +103,7 @@ public class OAuthClient {
 
         driver.findElement(By.id("username")).sendKeys(username);
         driver.findElement(By.id("password")).sendKeys(password);
-        driver.findElement(By.cssSelector("input[type=\"submit\"]")).click();
+        driver.findElement(By.name("login")).click();
     }
 
     public AccessTokenResponse doAccessTokenRequest(String code, String password) {
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginPage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginPage.java
index 28e87cf..779873f 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginPage.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginPage.java
@@ -44,9 +44,12 @@ public class LoginPage extends AbstractPage {
     @FindBy(id = "totp")
     private WebElement totp;
 
-    @FindBy(css = "input[type=\"submit\"]")
+    @FindBy(name = "login")
     private WebElement submitButton;
 
+    @FindBy(name = "cancel")
+    private WebElement cancelButton;
+
     @FindBy(linkText = "Register")
     private WebElement registerLink;
 
@@ -66,17 +69,8 @@ public class LoginPage extends AbstractPage {
         submitButton.click();
     }
 
-    public void loginTotp(String username, String password, String code) {
-        usernameInput.clear();
-        usernameInput.sendKeys(username);
-
-        passwordInput.clear();
-        passwordInput.sendKeys(password);
-
-        totp.clear();
-        totp.sendKeys(code);
-
-        submitButton.click();
+    public void cancel() {
+        cancelButton.click();
     }
 
     public String getError() {
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/RegisterPage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/RegisterPage.java
index c1b52c5..c67d692 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/RegisterPage.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/RegisterPage.java
@@ -54,26 +54,32 @@ public class RegisterPage extends AbstractPage {
     private WebElement loginErrorMessage;
 
     public void register(String firstName, String lastName, String email, String username, String password, String passwordConfirm) {
+        firstNameInput.clear();
         if (firstName != null) {
             firstNameInput.sendKeys(firstName);
         }
 
+        lastNameInput.clear();
         if (lastName != null) {
             lastNameInput.sendKeys(lastName);
         }
 
+        emailInput.clear();
         if (email != null) {
             emailInput.sendKeys(email);
         }
 
+        usernameInput.clear();
         if (username != null) {
             usernameInput.sendKeys(username);
         }
 
+        passwordInput.clear();
         if (password != null) {
             passwordInput.sendKeys(password);
         }
 
+        passwordConfirmInput.clear();
         if (passwordConfirm != null) {
             passwordConfirmInput.sendKeys(passwordConfirm);
         }