keycloak-uncached

Merge pull request #118 from vrockai/KEYCLOAK-186 KEYCLOAK-186

12/4/2013 3:33:40 PM

Details

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..7894b09 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;
     };
 });
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 8b28419..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>
 
@@ -44,24 +45,85 @@
                                 <input id="oauth" type="text" ui-select2="userCredentialOptions" ng-model="realm.requiredOAuthClientCredentials" placeholder="Type a role and enter">
                             </div>
                         </div>
-                        <div class="form-group">
-                            <label for="policy">Password Policy <span class="required" data-ng-show="createRealm">*</span></label>
-
-                            <div class="controls">
-                                <input class="xlarge" type="text" id="policy" name="policy" data-ng-model="realm.passwordPolicy" autofocus required>
+                    </fieldset>
+                    <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>
-                        </div>
                     </fieldset>
-                   <div class="form-actions">
+                    <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-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;