KEYCLOAK-186 added UI for password policy settings
This commit is contained in:
parent
a7b653de28
commit
2596dcc9c3
4 changed files with 223 additions and 15 deletions
|
@ -13,12 +13,12 @@ select:-moz-focusring {
|
||||||
input[type="text"],
|
input[type="text"],
|
||||||
input[type="password"],
|
input[type="password"],
|
||||||
input[type="email"],
|
input[type="email"],
|
||||||
|
input[type="number"],
|
||||||
textarea {
|
textarea {
|
||||||
font-size: 1.1em;
|
font-size: 1.1em;
|
||||||
padding: 0 0.545454545454545em;
|
padding: 0 0.545454545454545em;
|
||||||
height: 2.36363636363636em;
|
height: 2.36363636363636em;
|
||||||
/* 26px */
|
/* 26px */
|
||||||
|
|
||||||
border: 1px #b6b6b6 solid;
|
border: 1px #b6b6b6 solid;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
box-shadow: inset 0px 2px 2px rgba(0, 0, 0, 0.1);
|
box-shadow: inset 0px 2px 2px rgba(0, 0, 0, 0.1);
|
||||||
|
@ -28,12 +28,14 @@ textarea {
|
||||||
input[type="text"]:hover,
|
input[type="text"]:hover,
|
||||||
input[type="password"]:hover,
|
input[type="password"]:hover,
|
||||||
input[type="email"]:hover,
|
input[type="email"]:hover,
|
||||||
|
input[type="number"]:hover,
|
||||||
textarea:hover {
|
textarea:hover {
|
||||||
border-color: #62afdb;
|
border-color: #62afdb;
|
||||||
}
|
}
|
||||||
input[type="text"]:focus,
|
input[type="text"]:focus,
|
||||||
input[type="password"]:focus,
|
input[type="password"]:focus,
|
||||||
input[type="email"]:focus,
|
input[type="email"]:focus,
|
||||||
|
input[type="number"]:focus,
|
||||||
textarea:focus {
|
textarea:focus {
|
||||||
border-color: #62afdb;
|
border-color: #62afdb;
|
||||||
box-shadow: #62afdb 0 0 5px;
|
box-shadow: #62afdb 0 0 5px;
|
||||||
|
@ -41,6 +43,7 @@ textarea:focus {
|
||||||
input[type="text"].error,
|
input[type="text"].error,
|
||||||
input[type="password"].error,
|
input[type="password"].error,
|
||||||
input[type="email"].error,
|
input[type="email"].error,
|
||||||
|
input[type="number"].error,
|
||||||
textarea.error {
|
textarea.error {
|
||||||
border-color: #ba1212;
|
border-color: #ba1212;
|
||||||
transition: all 0.33s ease-in-out;
|
transition: all 0.33s ease-in-out;
|
||||||
|
@ -50,36 +53,42 @@ textarea.error {
|
||||||
input[type="text"].error:focus,
|
input[type="text"].error:focus,
|
||||||
input[type="password"].error:focus,
|
input[type="password"].error:focus,
|
||||||
input[type="email"].error:focus,
|
input[type="email"].error:focus,
|
||||||
|
input[type="number"].error:focus,
|
||||||
textarea.error:focus {
|
textarea.error:focus {
|
||||||
box-shadow: 0 0 5px #ba1212;
|
box-shadow: 0 0 5px #ba1212;
|
||||||
}
|
}
|
||||||
input[type="text"].tiny,
|
input[type="text"].tiny,
|
||||||
input[type="password"].tiny,
|
input[type="password"].tiny,
|
||||||
input[type="email"].tiny,
|
input[type="email"].tiny,
|
||||||
|
input[type="number"].tiny,
|
||||||
textarea.tiny {
|
textarea.tiny {
|
||||||
width: 4.54545454545455em;
|
width: 4.54545454545455em;
|
||||||
}
|
}
|
||||||
input[type="text"].small,
|
input[type="text"].small,
|
||||||
input[type="password"].small,
|
input[type="password"].small,
|
||||||
input[type="email"].small,
|
input[type="email"].small,
|
||||||
|
input[type="number"].small,
|
||||||
textarea.small {
|
textarea.small {
|
||||||
width: 9.09090909090909em;
|
width: 9.09090909090909em;
|
||||||
}
|
}
|
||||||
input[type="text"].medium,
|
input[type="text"].medium,
|
||||||
input[type="password"].medium,
|
input[type="password"].medium,
|
||||||
input[type="email"].medium,
|
input[type="email"].medium,
|
||||||
|
input[type="number"].medium,
|
||||||
textarea.medium {
|
textarea.medium {
|
||||||
width: 18.1818em;
|
width: 18.1818em;
|
||||||
}
|
}
|
||||||
input[type="text"].large,
|
input[type="text"].large,
|
||||||
input[type="password"].large,
|
input[type="password"].large,
|
||||||
input[type="email"].large,
|
input[type="email"].large,
|
||||||
|
input[type="number"].large,
|
||||||
textarea.large {
|
textarea.large {
|
||||||
width: 27.2727272727273em;
|
width: 27.2727272727273em;
|
||||||
}
|
}
|
||||||
input[type="text"].xlarge,
|
input[type="text"].xlarge,
|
||||||
input[type="password"].xlarge,
|
input[type="password"].xlarge,
|
||||||
input[type="email"].xlarge,
|
input[type="email"].xlarge,
|
||||||
|
input[type="number"].xlarge,
|
||||||
textarea.xlarge {
|
textarea.xlarge {
|
||||||
width: 36.3636363636364em;
|
width: 36.3636363636364em;
|
||||||
}
|
}
|
||||||
|
@ -90,18 +99,21 @@ textarea {
|
||||||
input[type="text"][readonly],
|
input[type="text"][readonly],
|
||||||
input[type="password"][readonly],
|
input[type="password"][readonly],
|
||||||
input[type="email"][readonly],
|
input[type="email"][readonly],
|
||||||
|
input[type="number"][readonly],
|
||||||
textarea[readonly] {
|
textarea[readonly] {
|
||||||
background-color: #f0f0f0;
|
background-color: #f0f0f0;
|
||||||
}
|
}
|
||||||
input[type="text"][readonly]:hover,
|
input[type="text"][readonly]:hover,
|
||||||
input[type="password"][readonly]:hover,
|
input[type="password"][readonly]:hover,
|
||||||
input[type="email"][readonly]:hover,
|
input[type="email"][readonly]:hover,
|
||||||
|
input[type="number"][readonly]:hover,
|
||||||
textarea[readonly]:hover {
|
textarea[readonly]:hover {
|
||||||
border-color: #62afdb;
|
border-color: #62afdb;
|
||||||
}
|
}
|
||||||
input[type="text"][readonly]:focus,
|
input[type="text"][readonly]:focus,
|
||||||
input[type="password"][readonly]:focus,
|
input[type="password"][readonly]:focus,
|
||||||
input[type="email"][readonly]:focus,
|
input[type="email"][readonly]:focus,
|
||||||
|
input[type="number"][readonly]:focus,
|
||||||
textarea[readonly]:focus {
|
textarea[readonly]:focus {
|
||||||
border-color: #b6b6b6;
|
border-color: #b6b6b6;
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
|
|
|
@ -20,6 +20,7 @@ select:-moz-focusring {
|
||||||
input[type="text"],
|
input[type="text"],
|
||||||
input[type="password"],
|
input[type="password"],
|
||||||
input[type="email"],
|
input[type="email"],
|
||||||
|
input[type="number"],
|
||||||
textarea {
|
textarea {
|
||||||
font-size: 1.1em;
|
font-size: 1.1em;
|
||||||
padding: 0 0.545454545454545em;
|
padding: 0 0.545454545454545em;
|
||||||
|
@ -79,6 +80,7 @@ textarea {
|
||||||
input[type="text"][readonly],
|
input[type="text"][readonly],
|
||||||
input[type="password"][readonly],
|
input[type="password"][readonly],
|
||||||
input[type="email"][readonly],
|
input[type="email"][readonly],
|
||||||
|
input[type="number"][readonly],
|
||||||
textarea[readonly] {
|
textarea[readonly] {
|
||||||
background-color: #f0f0f0;
|
background-color: #f0f0f0;
|
||||||
|
|
||||||
|
|
|
@ -181,13 +181,131 @@ module.controller('RealmRequiredCredentialsCtrl', function($scope, Realm, realm,
|
||||||
registrationAllowed : realm.registrationAllowed
|
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 = {
|
$scope.userCredentialOptions = {
|
||||||
'multiple' : true,
|
'multiple' : true,
|
||||||
'simple_tags' : true,
|
'simple_tags' : true,
|
||||||
'tags' : ['password', 'totp', 'cert']
|
'tags' : ['password', 'totp', 'cert']
|
||||||
};
|
};
|
||||||
|
|
||||||
var oldCopy = angular.copy($scope.realm);
|
|
||||||
$scope.changed = false;
|
$scope.changed = false;
|
||||||
|
|
||||||
$scope.$watch('realm', function() {
|
$scope.$watch('realm', function() {
|
||||||
|
@ -196,17 +314,31 @@ module.controller('RealmRequiredCredentialsCtrl', function($scope, Realm, realm,
|
||||||
}
|
}
|
||||||
}, true);
|
}, true);
|
||||||
|
|
||||||
|
$scope.$watch('policy', function() {
|
||||||
|
$scope.realm.passwordPolicy = generatePolicy($scope.policy);
|
||||||
|
if ($scope.realm.passwordPolicy != realm.passwordPolicy){
|
||||||
|
$scope.changed = true;
|
||||||
|
}
|
||||||
|
}, true);
|
||||||
|
|
||||||
$scope.save = function() {
|
$scope.save = function() {
|
||||||
var realmCopy = angular.copy($scope.realm);
|
|
||||||
$scope.changed = false;
|
$scope.changed = false;
|
||||||
Realm.update(realmCopy, function () {
|
|
||||||
|
Realm.update($scope.realm, function () {
|
||||||
$location.url("/realms/" + realm.id + "/required-credentials");
|
$location.url("/realms/" + realm.id + "/required-credentials");
|
||||||
Notifications.success("Your changes have been saved to the realm.");
|
Notifications.success("Your changes have been saved to the realm.");
|
||||||
|
oldCopy = angular.copy($scope.realm);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.reset = function() {
|
$scope.reset = function() {
|
||||||
$scope.realm = angular.copy(oldCopy);
|
$scope.realm = angular.copy(oldCopy);
|
||||||
|
|
||||||
|
$scope.configuredPolicies = [];
|
||||||
|
$scope.availablePolicies = $scope.allPolicies.slice(0);
|
||||||
|
$scope.policy = evaluatePolicy(oldCopy.passwordPolicy);
|
||||||
|
updateConfigured();
|
||||||
|
|
||||||
$scope.changed = false;
|
$scope.changed = false;
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<div id="wrapper" class="container">
|
<div id="wrapper" class="container realm-policy">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="bs-sidebar col-md-3 clearfix" data-ng-include data-src="'partials/realm-menu.html'"></div>
|
<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">
|
<div id="content-area" class="col-md-9" role="main">
|
||||||
|
@ -23,6 +23,7 @@
|
||||||
<h2><span>{{realm.realm}}</span> Credentials</h2>
|
<h2><span>{{realm.realm}}</span> Credentials</h2>
|
||||||
<form name="realmForm" novalidate>
|
<form name="realmForm" novalidate>
|
||||||
<fieldset class="border-top">
|
<fieldset class="border-top">
|
||||||
|
<legend uncollapsed><span class="text">Realm Credentials Settings</span></legend>
|
||||||
<div class="form-group clearfix">
|
<div class="form-group clearfix">
|
||||||
<label for="user" class="control-label two-lines">Required User Credentials</label>
|
<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">
|
<input id="oauth" type="text" ui-select2="userCredentialOptions" ng-model="realm.requiredOAuthClientCredentials" placeholder="Type a role and enter">
|
||||||
</div>
|
</div>
|
||||||
</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>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</fieldset>
|
</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 type="submit" kc-save class="primary" data-ng-show="changed">Save
|
||||||
</button>
|
</button>
|
||||||
<button type="submit" kc-reset data-ng-show="changed">Clear changes
|
<button type="submit" kc-reset data-ng-show="changed">Clear changes
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="container-right-bg"></div>
|
<div id="container-right-bg"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</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>
|
Loading…
Reference in a new issue