Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
bab866eaae
45 changed files with 1037 additions and 146 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;
|
||||||
|
|
||||||
|
|
|
@ -48,7 +48,11 @@
|
||||||
<script src="js/loaders.js"></script>
|
<script src="js/loaders.js"></script>
|
||||||
<script src="js/services.js"></script>
|
<script src="js/services.js"></script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
[ng\:cloak], [ng-cloak], .ng-cloak {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body class="admin-console" data-ng-controller="GlobalCtrl" ng-cloak>
|
<body class="admin-console" data-ng-controller="GlobalCtrl" ng-cloak>
|
||||||
|
|
|
@ -535,6 +535,7 @@ module.directive('onoffswitch', function() {
|
||||||
replace: true,
|
replace: true,
|
||||||
scope: {
|
scope: {
|
||||||
ngModel: '=',
|
ngModel: '=',
|
||||||
|
ngDisabled: '=',
|
||||||
ngBind: '=',
|
ngBind: '=',
|
||||||
name: '=',
|
name: '=',
|
||||||
id: '=',
|
id: '=',
|
||||||
|
@ -544,9 +545,11 @@ module.directive('onoffswitch', function() {
|
||||||
compile: function(element, attrs) {
|
compile: function(element, attrs) {
|
||||||
if (!attrs.onText) { attrs.onText = "ON"; }
|
if (!attrs.onText) { attrs.onText = "ON"; }
|
||||||
if (!attrs.offText) { attrs.offText = "OFF"; }
|
if (!attrs.offText) { attrs.offText = "OFF"; }
|
||||||
|
if (!attrs.ngDisabled) { attrs.ngDisabled = false; }
|
||||||
|
|
||||||
var html = "<div class=\"onoffswitch\">" +
|
var html = "<span><div class=\"onoffswitch\" data-ng-class=\"{disabled: ngDisabled}\">" +
|
||||||
"<input type=\"checkbox\" data-ng-model=\"ngModel\" class=\"onoffswitch-checkbox\" name=\"" + attrs.name + "\" id=\"" + attrs.id + "\">" +
|
"<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\">" +
|
"<label for=\"" + attrs.id + "\" class=\"onoffswitch-label\">" +
|
||||||
"<span class=\"onoffswitch-inner\">" +
|
"<span class=\"onoffswitch-inner\">" +
|
||||||
"<span class=\"onoffswitch-active\">{{onText}}</span>" +
|
"<span class=\"onoffswitch-active\">{{onText}}</span>" +
|
||||||
|
@ -554,14 +557,13 @@ module.directive('onoffswitch', function() {
|
||||||
"</span>" +
|
"</span>" +
|
||||||
"<span class=\"onoffswitch-switch\"></span>" +
|
"<span class=\"onoffswitch-switch\"></span>" +
|
||||||
"</label>" +
|
"</label>" +
|
||||||
"</div>";
|
"</div></span>";
|
||||||
|
|
||||||
element.replaceWith($(html));
|
element.replaceWith($(html));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
module.directive('kcInput', function() {
|
module.directive('kcInput', function() {
|
||||||
var d = {
|
var d = {
|
||||||
scope : true,
|
scope : true,
|
||||||
|
|
|
@ -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;
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
@ -430,7 +562,7 @@ module.controller('RealmSocialCtrl', function($scope, realm, Realm, $location, N
|
||||||
$scope.realm.socialProviders[$scope.newProviderId+".key"]="";
|
$scope.realm.socialProviders[$scope.newProviderId+".key"]="";
|
||||||
$scope.realm.socialProviders[$scope.newProviderId+".secret"]="";
|
$scope.realm.socialProviders[$scope.newProviderId+".secret"]="";
|
||||||
$scope.configuredProviders.push($scope.newProviderId);
|
$scope.configuredProviders.push($scope.newProviderId);
|
||||||
$scope.unsetProviders.remove($scope.unsetProviders.indexOf($scope.newProviderId));
|
$scope.unsetProviders.splice($scope.unsetProviders.indexOf($scope.newProviderId),1);
|
||||||
selectFirstProvider();
|
selectFirstProvider();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -438,12 +570,12 @@ module.controller('RealmSocialCtrl', function($scope, realm, Realm, $location, N
|
||||||
$scope.removeProvider = function(pId) {
|
$scope.removeProvider = function(pId) {
|
||||||
delete $scope.realm.socialProviders[pId+".key"];
|
delete $scope.realm.socialProviders[pId+".key"];
|
||||||
delete $scope.realm.socialProviders[pId+".secret"];
|
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
|
// 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);
|
var rId = $scope.postSaveProviders.indexOf(pId);
|
||||||
if (rId > -1){
|
if (rId > -1){
|
||||||
$scope.postSaveProviders.remove(rId)
|
$scope.postSaveProviders.splice(rId,1)
|
||||||
}
|
}
|
||||||
|
|
||||||
$scope.unsetProviders.push(pId);
|
$scope.unsetProviders.push(pId);
|
||||||
|
|
|
@ -231,8 +231,14 @@ module.controller('UserCredentialsCtrl', function($scope, realm, user, User, Use
|
||||||
$scope.realm = realm;
|
$scope.realm = realm;
|
||||||
$scope.user = angular.copy(user);
|
$scope.user = angular.copy(user);
|
||||||
|
|
||||||
|
$scope.isTotp = false;
|
||||||
|
if(!!user.totp){
|
||||||
|
$scope.isTotp = user.totp;
|
||||||
|
}
|
||||||
|
|
||||||
$scope.resetPassword = function() {
|
$scope.resetPassword = function() {
|
||||||
|
|
||||||
|
if ($scope.pwdChange) {
|
||||||
if ($scope.password != $scope.confirmPassword) {
|
if ($scope.password != $scope.confirmPassword) {
|
||||||
Notifications.error("Password and confirmation does not match.");
|
Notifications.error("Password and confirmation does not match.");
|
||||||
$scope.password = "";
|
$scope.password = "";
|
||||||
|
@ -246,6 +252,7 @@ module.controller('UserCredentialsCtrl', function($scope, realm, user, User, Use
|
||||||
if ($scope.user.requiredActions.indexOf("UPDATE_PASSWORD") < 0){
|
if ($scope.user.requiredActions.indexOf("UPDATE_PASSWORD") < 0){
|
||||||
$scope.user.requiredActions.push("UPDATE_PASSWORD");
|
$scope.user.requiredActions.push("UPDATE_PASSWORD");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var credentials = [ { type : "password", value : $scope.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
|
userId: $scope.user.username
|
||||||
}, $scope.user, function () {
|
}, $scope.user, function () {
|
||||||
|
|
||||||
|
$scope.isTotp = $scope.user.totp;
|
||||||
|
|
||||||
|
if ($scope.pwdChange){
|
||||||
UserCredentials.update({
|
UserCredentials.update({
|
||||||
realm: realm.id,
|
realm: realm.id,
|
||||||
userId: $scope.user.username
|
userId: $scope.user.username
|
||||||
}, credentials, function () {
|
}, credentials, function () {
|
||||||
Notifications.success("The user password has been reset. The user is required to change his password on" +
|
Notifications.success("The password has been reset. The user is required to change his password on" +
|
||||||
" the next login.");
|
" the next login.");
|
||||||
|
$scope.password = "";
|
||||||
|
$scope.confirmPassword = "";
|
||||||
|
$scope.pwdChange = false;
|
||||||
|
$scope.isTotp = user.totp;
|
||||||
|
$scope.userChange = false;
|
||||||
}, function () {
|
}, function () {
|
||||||
Notifications.error("Error while resetting user password. Be aware that the update password required action" +
|
Notifications.error("Error while resetting user password. Be aware that the update password required action" +
|
||||||
" was already set.");
|
" was already set.");
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
Notifications.success("User settings was updated.");
|
||||||
|
$scope.isTotp = user.totp;
|
||||||
|
$scope.userChange = false;
|
||||||
|
}
|
||||||
|
|
||||||
}, function () {
|
}, 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) {
|
module.controller('RoleMappingCtrl', function($scope, realm, User, users, role, RoleMapping, Notifications) {
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
@ -45,16 +46,84 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</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>
|
||||||
|
</fieldset>
|
||||||
<div class="form-actions">
|
<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>
|
|
@ -1,5 +1,7 @@
|
||||||
<ul data-ng-hide="createRealm">
|
<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 data-ng-class="(path[2] == 'users' || path[1] == 'user') && 'active'"><a href="#/realms/{{realm.id}}/users">Users</a>
|
||||||
</li>
|
</li>
|
||||||
<li data-ng-class="(path[2] == 'applications' || path[1] == 'application') && 'active'"><a href="#/realms/{{realm.id}}/applications">Applications</a></li>
|
<li data-ng-class="(path[2] == 'applications' || path[1] == 'application') && 'active'"><a href="#/realms/{{realm.id}}/applications">Applications</a></li>
|
||||||
|
|
|
@ -14,10 +14,11 @@
|
||||||
<li><a href="#/realms/{{realm.id}}">{{realm.realm}}</a></li>
|
<li><a href="#/realms/{{realm.id}}">{{realm.realm}}</a></li>
|
||||||
<li class="active">Users</li>
|
<li class="active">Users</li>
|
||||||
</ol>
|
</ol>
|
||||||
<h2><span>{{user.username}}'s</span> Reset Password</h2>
|
<h2><span>{{user.username}}'s</span> Credentials</h2>
|
||||||
|
|
||||||
<form name="userForm" novalidate>
|
<form name="userForm" novalidate>
|
||||||
<fieldset class="border-top">
|
<fieldset class="border-top">
|
||||||
|
<legend uncollapsed><span class="text">Reset Password</span></legend>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="password">New Password</label>
|
<label for="password">New Password</label>
|
||||||
<div class="controls">
|
<div class="controls">
|
||||||
|
@ -28,14 +29,20 @@
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="two-lines" for="password">New Password Confirmation</label>
|
<label class="two-lines" for="password">New Password Confirmation</label>
|
||||||
<div class="controls">
|
<div class="controls">
|
||||||
<input type="password" id="confirmPassword" name="confirmPassword" data-ng-model="confirmPassword"
|
<input type="password" id="confirmPassword" name="confirmPassword"
|
||||||
required>
|
data-ng-model="confirmPassword" required>
|
||||||
</div>
|
</div>
|
||||||
</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>
|
</fieldset>
|
||||||
<div class="form-actions">
|
<div class="form-actions">
|
||||||
<button type="submit" class="primary" data-ng-click="resetPassword()"
|
<button type="submit" data-ng-click="resetPassword()" class="primary" data-ng-show="userChange && !pwdChange">Save</button>
|
||||||
ng-show="password != null">Reset Password</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>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
|
|
@ -70,10 +70,6 @@
|
||||||
<label for="userEnabled" class="control-label">User Enabled</label>
|
<label for="userEnabled" class="control-label">User Enabled</label>
|
||||||
<input ng-model="user.enabled" name="userEnabled" id="userEnabled" onoffswitch />
|
<input ng-model="user.enabled" name="userEnabled" id="userEnabled" onoffswitch />
|
||||||
</div>
|
</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">
|
<div class="form-group clearfix block">
|
||||||
<label for="emailVerified" class="control-label">Email verified</label>
|
<label for="emailVerified" class="control-label">Email verified</label>
|
||||||
<input ng-model="user.emailVerified" name="emailVerified" id="emailVerified" onoffswitch />
|
<input ng-model="user.emailVerified" name="emailVerified" id="emailVerified" onoffswitch />
|
||||||
|
|
|
@ -32,6 +32,7 @@ public class RealmRepresentation {
|
||||||
protected Set<String> requiredCredentials;
|
protected Set<String> requiredCredentials;
|
||||||
protected Set<String> requiredApplicationCredentials;
|
protected Set<String> requiredApplicationCredentials;
|
||||||
protected Set<String> requiredOAuthClientCredentials;
|
protected Set<String> requiredOAuthClientCredentials;
|
||||||
|
protected String passwordPolicy;
|
||||||
protected List<UserRepresentation> users;
|
protected List<UserRepresentation> users;
|
||||||
protected List<UserRoleMappingRepresentation> roleMappings;
|
protected List<UserRoleMappingRepresentation> roleMappings;
|
||||||
protected List<ScopeMappingRepresentation> scopeMappings;
|
protected List<ScopeMappingRepresentation> scopeMappings;
|
||||||
|
@ -199,6 +200,14 @@ public class RealmRepresentation {
|
||||||
this.requiredOAuthClientCredentials = requiredOAuthClientCredentials;
|
this.requiredOAuthClientCredentials = requiredOAuthClientCredentials;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getPasswordPolicy() {
|
||||||
|
return passwordPolicy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPasswordPolicy(String passwordPolicy) {
|
||||||
|
this.passwordPolicy = passwordPolicy;
|
||||||
|
}
|
||||||
|
|
||||||
public Integer getAccessCodeLifespan() {
|
public Integer getAccessCodeLifespan() {
|
||||||
return accessCodeLifespan;
|
return accessCodeLifespan;
|
||||||
}
|
}
|
||||||
|
|
|
@ -106,6 +106,10 @@
|
||||||
<groupId>de.flapdoodle.embed</groupId>
|
<groupId>de.flapdoodle.embed</groupId>
|
||||||
<artifactId>de.flapdoodle.embed.mongo</artifactId>
|
<artifactId>de.flapdoodle.embed.mongo</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.jboss.spec.javax.servlet</groupId>
|
||||||
|
<artifactId>jboss-servlet-api_3.0_spec</artifactId>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>junit</groupId>
|
<groupId>junit</groupId>
|
||||||
<artifactId>junit</artifactId>
|
<artifactId>junit</artifactId>
|
||||||
|
|
|
@ -38,8 +38,6 @@ public class LoginBean {
|
||||||
|
|
||||||
private String password;
|
private String password;
|
||||||
|
|
||||||
private List<RequiredCredential> requiredCredentials;
|
|
||||||
|
|
||||||
public LoginBean(RealmBean realm, MultivaluedMap<String, String> formData){
|
public LoginBean(RealmBean realm, MultivaluedMap<String, String> formData){
|
||||||
|
|
||||||
this.realm = realm;
|
this.realm = realm;
|
||||||
|
@ -48,14 +46,6 @@ public class LoginBean {
|
||||||
username = formData.getFirst("username");
|
username = formData.getFirst("username");
|
||||||
password = formData.getFirst("password");
|
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() {
|
public String getUsername() {
|
||||||
|
@ -66,10 +56,6 @@ public class LoginBean {
|
||||||
return password;
|
return password;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<RequiredCredential> getRequiredCredentials() {
|
|
||||||
return requiredCredentials;
|
|
||||||
}
|
|
||||||
|
|
||||||
public RealmBean getRealm() {
|
public RealmBean getRealm() {
|
||||||
return realm;
|
return realm;
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,8 @@ package org.keycloak.forms;
|
||||||
|
|
||||||
import org.keycloak.services.resources.flows.FormFlows;
|
import org.keycloak.services.resources.flows.FormFlows;
|
||||||
|
|
||||||
|
import java.util.ResourceBundle;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
*/
|
*/
|
||||||
|
@ -32,13 +34,12 @@ public class MessageBean {
|
||||||
|
|
||||||
private FormFlows.MessageType type;
|
private FormFlows.MessageType type;
|
||||||
|
|
||||||
// Message is considered ERROR by default
|
public MessageBean(String summary, FormFlows.MessageType type, ResourceBundle rb) {
|
||||||
public MessageBean(String summary) {
|
if (rb.containsKey(summary)) {
|
||||||
this(summary, FormFlows.MessageType.ERROR);
|
this.summary = rb.getString(summary);
|
||||||
}
|
} else {
|
||||||
|
|
||||||
public MessageBean(String summary, FormFlows.MessageType type) {
|
|
||||||
this.summary = summary;
|
this.summary = summary;
|
||||||
|
}
|
||||||
this.type = type;
|
this.type = type;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -79,19 +79,18 @@ public class FormServiceImpl implements FormService {
|
||||||
}
|
}
|
||||||
|
|
||||||
public String process(String pageId, FormServiceDataBean dataBean){
|
public String process(String pageId, FormServiceDataBean dataBean){
|
||||||
|
|
||||||
Map<String, Object> attributes = new HashMap<String, Object>();
|
Map<String, Object> attributes = new HashMap<String, Object>();
|
||||||
|
|
||||||
|
ResourceBundle rb = ResourceBundle.getBundle(BUNDLE);
|
||||||
|
attributes.put("rb", rb);
|
||||||
|
|
||||||
if (dataBean.getMessage() != null){
|
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());
|
RealmBean realm = new RealmBean(dataBean.getRealm());
|
||||||
attributes.put("template", new TemplateBean(realm, dataBean.getContextPath()));
|
attributes.put("template", new TemplateBean(realm, dataBean.getContextPath()));
|
||||||
|
|
||||||
ResourceBundle rb = ResourceBundle.getBundle(BUNDLE);
|
|
||||||
attributes.put("rb", rb);
|
|
||||||
|
|
||||||
if (commandMap.containsKey(pageId)){
|
if (commandMap.containsKey(pageId)){
|
||||||
commandMap.get(pageId).exec(attributes, dataBean);
|
commandMap.get(pageId).exec(attributes, dataBean);
|
||||||
}
|
}
|
||||||
|
|
|
@ -129,8 +129,8 @@ body {
|
||||||
margin-bottom: 0.54545454545455em;
|
margin-bottom: 0.54545454545455em;
|
||||||
/* 6px */
|
/* 6px */
|
||||||
}
|
}
|
||||||
.rcue-login-register form > input[type="button"],
|
.rcue-login-register div.form-buttons {
|
||||||
.rcue-login-register form > input[type="submit"]{
|
display: inline;
|
||||||
float: right;
|
float: right;
|
||||||
margin-top: 0.76923076923077em;
|
margin-top: 0.76923076923077em;
|
||||||
/* 10px */
|
/* 10px */
|
||||||
|
|
|
@ -16,13 +16,14 @@
|
||||||
<label for="username">${rb.getString('username')}</label><input id="username" name="username" value="${login.username!''}" type="text" autofocus />
|
<label for="username">${rb.getString('username')}</label><input id="username" name="username" value="${login.username!''}" type="text" autofocus />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<#list login.requiredCredentials as c>
|
|
||||||
<div>
|
<div>
|
||||||
<label for="${c.name}">${rb.getString(c.label)}</label><input id="${c.name}" name="${c.name}" type="${c.inputType}" />
|
<label for="password">${rb.getString('password')}</label><input id="password" name="password" type="password" />
|
||||||
</div>
|
</div>
|
||||||
</#list>
|
|
||||||
|
|
||||||
<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">
|
<div class="aside-btn">
|
||||||
<p>Forgot <a href="${url.loginPasswordResetUrl}">Password</a>?</p>
|
<p>Forgot <a href="${url.loginPasswordResetUrl}">Password</a>?</p>
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
<div class="feedback-aligner">
|
<div class="feedback-aligner">
|
||||||
<#if message?has_content && message.warning>
|
<#if message?has_content && message.warning>
|
||||||
<div class="feedback warning show">
|
<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>
|
</div>
|
||||||
</#if>
|
</#if>
|
||||||
</div>
|
</div>
|
||||||
|
@ -47,13 +47,13 @@
|
||||||
<#if message.error>
|
<#if message.error>
|
||||||
<div class="feedback error bottom-left show">
|
<div class="feedback error bottom-left show">
|
||||||
<p>
|
<p>
|
||||||
<strong id="loginError">${rb.getString(message.summary)}</strong><br/>${rb.getString('emailErrorInfo')}
|
<strong id="loginError">${message.summary}</strong><br/>${rb.getString('emailErrorInfo')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<#elseif message.success>
|
<#elseif message.success>
|
||||||
<div class="feedback success bottom-left show">
|
<div class="feedback success bottom-left show">
|
||||||
<p>
|
<p>
|
||||||
<strong>${rb.getString('successHeader')}</strong> ${rb.getString(message.summary)}
|
<strong>${rb.getString('successHeader')}</strong> ${message.summary}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</#if>
|
</#if>
|
||||||
|
|
|
@ -40,7 +40,7 @@
|
||||||
<#if message?has_content && message.error>
|
<#if message?has_content && message.error>
|
||||||
<div class="feedback error bottom-left show">
|
<div class="feedback error bottom-left show">
|
||||||
<p>
|
<p>
|
||||||
<strong id="loginError">${rb.getString(message.summary)}</strong><br/>${rb.getString('emailErrorInfo')}
|
<strong id="loginError">${message.summary}</strong><br/>${rb.getString('emailErrorInfo')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</#if>
|
</#if>
|
||||||
|
|
|
@ -31,10 +31,10 @@
|
||||||
<#if message?has_content>
|
<#if message?has_content>
|
||||||
<div class="feedback-aligner">
|
<div class="feedback-aligner">
|
||||||
<#if message.success>
|
<#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>
|
||||||
<#if message.error>
|
<#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>
|
</#if>
|
||||||
</div>
|
</div>
|
||||||
</#if>
|
</#if>
|
||||||
|
|
|
@ -13,6 +13,11 @@
|
||||||
<description/>
|
<description/>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>junit</groupId>
|
||||||
|
<artifactId>junit</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
<build>
|
<build>
|
||||||
<plugins>
|
<plugins>
|
||||||
|
|
182
model/api/src/main/java/org/keycloak/models/PasswordPolicy.java
Normal file
182
model/api/src/main/java/org/keycloak/models/PasswordPolicy.java
Normal file
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -74,6 +74,10 @@ public interface RealmModel extends RoleContainerModel, RoleMapperModel, ScopeMa
|
||||||
|
|
||||||
void addRequiredCredential(String cred);
|
void addRequiredCredential(String cred);
|
||||||
|
|
||||||
|
PasswordPolicy getPasswordPolicy();
|
||||||
|
|
||||||
|
void setPasswordPolicy(PasswordPolicy policy);
|
||||||
|
|
||||||
boolean validatePassword(UserModel user, String password);
|
boolean validatePassword(UserModel user, String password);
|
||||||
|
|
||||||
boolean validateTOTP(UserModel user, String password, String token);
|
boolean validateTOTP(UserModel user, String password, String token);
|
||||||
|
|
|
@ -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&-"));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -4,6 +4,7 @@ import org.bouncycastle.openssl.PEMWriter;
|
||||||
import org.keycloak.PemUtils;
|
import org.keycloak.PemUtils;
|
||||||
import org.keycloak.models.ApplicationModel;
|
import org.keycloak.models.ApplicationModel;
|
||||||
import org.keycloak.models.OAuthClientModel;
|
import org.keycloak.models.OAuthClientModel;
|
||||||
|
import org.keycloak.models.PasswordPolicy;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.RequiredCredentialModel;
|
import org.keycloak.models.RequiredCredentialModel;
|
||||||
import org.keycloak.models.RoleModel;
|
import org.keycloak.models.RoleModel;
|
||||||
|
@ -37,6 +38,7 @@ public class RealmAdapter implements RealmModel {
|
||||||
protected EntityManager em;
|
protected EntityManager em;
|
||||||
protected volatile transient PublicKey publicKey;
|
protected volatile transient PublicKey publicKey;
|
||||||
protected volatile transient PrivateKey privateKey;
|
protected volatile transient PrivateKey privateKey;
|
||||||
|
private PasswordPolicy passwordPolicy;
|
||||||
|
|
||||||
public RealmAdapter(EntityManager em, RealmEntity realm) {
|
public RealmAdapter(EntityManager em, RealmEntity realm) {
|
||||||
this.em = em;
|
this.em = em;
|
||||||
|
@ -1037,4 +1039,18 @@ public class RealmAdapter implements RealmModel {
|
||||||
em.flush();
|
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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,6 +34,7 @@ public class RealmEntity {
|
||||||
protected boolean resetPasswordAllowed;
|
protected boolean resetPasswordAllowed;
|
||||||
protected boolean social;
|
protected boolean social;
|
||||||
protected boolean automaticRegistrationAfterSocialLogin;
|
protected boolean automaticRegistrationAfterSocialLogin;
|
||||||
|
protected String passwordPolicy;
|
||||||
|
|
||||||
protected int tokenLifespan;
|
protected int tokenLifespan;
|
||||||
protected int accessCodeLifespan;
|
protected int accessCodeLifespan;
|
||||||
|
@ -269,4 +270,13 @@ public class RealmEntity {
|
||||||
public void setDefaultRoles(Collection<RoleEntity> defaultRoles) {
|
public void setDefaultRoles(Collection<RoleEntity> defaultRoles) {
|
||||||
this.defaultRoles = defaultRoles;
|
this.defaultRoles = defaultRoles;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getPasswordPolicy() {
|
||||||
|
return passwordPolicy;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setPasswordPolicy(String passwordPolicy) {
|
||||||
|
this.passwordPolicy = passwordPolicy;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ import org.keycloak.models.ApplicationModel;
|
||||||
import org.keycloak.models.IdGenerator;
|
import org.keycloak.models.IdGenerator;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.OAuthClientModel;
|
import org.keycloak.models.OAuthClientModel;
|
||||||
|
import org.keycloak.models.PasswordPolicy;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.RequiredCredentialModel;
|
import org.keycloak.models.RequiredCredentialModel;
|
||||||
import org.keycloak.models.RoleModel;
|
import org.keycloak.models.RoleModel;
|
||||||
|
@ -62,6 +63,7 @@ public class RealmAdapter implements RealmModel {
|
||||||
protected PartitionManager partitionManager;
|
protected PartitionManager partitionManager;
|
||||||
protected RelationshipManager relationshipManager;
|
protected RelationshipManager relationshipManager;
|
||||||
protected KeycloakSession session;
|
protected KeycloakSession session;
|
||||||
|
private PasswordPolicy passwordPolicy;
|
||||||
|
|
||||||
public RealmAdapter(KeycloakSession session, RealmData realm, PartitionManager partitionManager) {
|
public RealmAdapter(KeycloakSession session, RealmData realm, PartitionManager partitionManager) {
|
||||||
this.session = session;
|
this.session = session;
|
||||||
|
@ -977,4 +979,19 @@ public class RealmAdapter implements RealmModel {
|
||||||
realm.setSocialConfig(socialConfig);
|
realm.setSocialConfig(socialConfig);
|
||||||
updateRealm();
|
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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,6 +27,7 @@ public class RealmData extends AbstractPartition {
|
||||||
private String[] defaultRoles;
|
private String[] defaultRoles;
|
||||||
private Map<String, String> smtpConfig;
|
private Map<String, String> smtpConfig;
|
||||||
private Map<String, String> socialConfig;
|
private Map<String, String> socialConfig;
|
||||||
|
private String passwordPolicy;
|
||||||
|
|
||||||
public RealmData() {
|
public RealmData() {
|
||||||
super(null);
|
super(null);
|
||||||
|
@ -185,4 +186,13 @@ public class RealmData extends AbstractPartition {
|
||||||
public void setSocialConfig(Map<String, String> socialConfig) {
|
public void setSocialConfig(Map<String, String> socialConfig) {
|
||||||
this.socialConfig = socialConfig;
|
this.socialConfig = socialConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@AttributeProperty
|
||||||
|
public String getPasswordPolicy() {
|
||||||
|
return passwordPolicy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPasswordPolicy(String passwordPolicy) {
|
||||||
|
this.passwordPolicy = passwordPolicy;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import org.keycloak.models.ApplicationModel;
|
||||||
import org.keycloak.models.Constants;
|
import org.keycloak.models.Constants;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.OAuthClientModel;
|
import org.keycloak.models.OAuthClientModel;
|
||||||
|
import org.keycloak.models.PasswordPolicy;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.RequiredCredentialModel;
|
import org.keycloak.models.RequiredCredentialModel;
|
||||||
import org.keycloak.models.RoleModel;
|
import org.keycloak.models.RoleModel;
|
||||||
|
@ -64,7 +65,7 @@ public class RealmManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
public RealmModel createRealm(String name) {
|
public RealmModel createRealm(String name) {
|
||||||
return createRealm(generateId(), name);
|
return createRealm(name, name);
|
||||||
}
|
}
|
||||||
|
|
||||||
public RealmModel createRealm(String id, String name) {
|
public RealmModel createRealm(String id, String name) {
|
||||||
|
@ -110,6 +111,9 @@ public class RealmManager {
|
||||||
if (rep.getRequiredApplicationCredentials() != null) {
|
if (rep.getRequiredApplicationCredentials() != null) {
|
||||||
realm.updateRequiredApplicationCredentials(rep.getRequiredApplicationCredentials());
|
realm.updateRequiredApplicationCredentials(rep.getRequiredApplicationCredentials());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
realm.setPasswordPolicy(new PasswordPolicy(rep.getPasswordPolicy()));
|
||||||
|
|
||||||
if (rep.getDefaultRoles() != null) {
|
if (rep.getDefaultRoles() != null) {
|
||||||
realm.updateDefaultRoles(rep.getDefaultRoles().toArray(new String[rep.getDefaultRoles().size()]));
|
realm.updateDefaultRoles(rep.getDefaultRoles().toArray(new String[rep.getDefaultRoles().size()]));
|
||||||
}
|
}
|
||||||
|
@ -222,6 +226,8 @@ public class RealmManager {
|
||||||
addOAuthClientRequiredCredential(newRealm, CredentialRepresentation.PASSWORD);
|
addOAuthClientRequiredCredential(newRealm, CredentialRepresentation.PASSWORD);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
newRealm.setPasswordPolicy(new PasswordPolicy(rep.getPasswordPolicy()));
|
||||||
|
|
||||||
if (rep.getUsers() != null) {
|
if (rep.getUsers() != null) {
|
||||||
for (UserRepresentation userRep : rep.getUsers()) {
|
for (UserRepresentation userRep : rep.getUsers()) {
|
||||||
UserModel user = createUser(newRealm, userRep);
|
UserModel user = createUser(newRealm, userRep);
|
||||||
|
@ -473,6 +479,9 @@ public class RealmManager {
|
||||||
rep.setAccessCodeLifespanUserAction(realm.getAccessCodeLifespanUserAction());
|
rep.setAccessCodeLifespanUserAction(realm.getAccessCodeLifespanUserAction());
|
||||||
rep.setSmtpServer(realm.getSmtpConfig());
|
rep.setSmtpServer(realm.getSmtpConfig());
|
||||||
rep.setSocialProviders(realm.getSocialConfig());
|
rep.setSocialProviders(realm.getSocialConfig());
|
||||||
|
if (realm.getPasswordPolicy() != null) {
|
||||||
|
rep.setPasswordPolicy(realm.getPasswordPolicy().toString());
|
||||||
|
}
|
||||||
|
|
||||||
ApplicationModel accountManagementApplication = realm.getApplicationNameMap().get(Constants.ACCOUNT_APPLICATION);
|
ApplicationModel accountManagementApplication = realm.getApplicationNameMap().get(Constants.ACCOUNT_APPLICATION);
|
||||||
rep.setAccountManagement(accountManagementApplication != null && accountManagementApplication.isEnabled());
|
rep.setAccountManagement(accountManagementApplication != null && accountManagementApplication.isEnabled());
|
||||||
|
|
|
@ -255,6 +255,11 @@ public class AccountService {
|
||||||
return forms.setError(Messages.INVALID_PASSWORD_EXISTING).forwardToPassword();
|
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();
|
UserCredentialModel credentials = new UserCredentialModel();
|
||||||
credentials.setType(CredentialRepresentation.PASSWORD);
|
credentials.setType(CredentialRepresentation.PASSWORD);
|
||||||
credentials.setValue(passwordNew);
|
credentials.setValue(passwordNew);
|
||||||
|
|
|
@ -183,7 +183,7 @@ public class TokenService {
|
||||||
@POST
|
@POST
|
||||||
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
|
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
|
||||||
public Response processLogin(@QueryParam("client_id") final String clientId, @QueryParam("scope") final String scopeParam,
|
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) {
|
final MultivaluedMap<String, String> formData) {
|
||||||
logger.debug("TokenService.processLogin");
|
logger.debug("TokenService.processLogin");
|
||||||
OAuthFlows oauth = Flows.oauth(realm, request, uriInfo, authManager, tokenManager);
|
OAuthFlows oauth = Flows.oauth(realm, request, uriInfo, authManager, tokenManager);
|
||||||
|
@ -199,6 +199,15 @@ public class TokenService {
|
||||||
return oauth.forwardToSecurityFailure("Login requester not enabled.");
|
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");
|
String username = formData.getFirst("username");
|
||||||
UserModel user = realm.getUser(username);
|
UserModel user = realm.getUser(username);
|
||||||
|
|
||||||
|
@ -286,6 +295,11 @@ public class TokenService {
|
||||||
return oauth.forwardToSecurityFailure("Login requester not enabled.");
|
return oauth.forwardToSecurityFailure("Login requester not enabled.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
redirect = verifyRedirectUri(redirect, client);
|
||||||
|
if (redirect == null) {
|
||||||
|
return oauth.forwardToSecurityFailure("Invalid redirect_uri.");
|
||||||
|
}
|
||||||
|
|
||||||
if (!realm.isRegistrationAllowed()) {
|
if (!realm.isRegistrationAllowed()) {
|
||||||
logger.warn("Registration not allowed");
|
logger.warn("Registration not allowed");
|
||||||
return oauth.forwardToSecurityFailure("Registration not allowed");
|
return oauth.forwardToSecurityFailure("Registration not allowed");
|
||||||
|
@ -297,6 +311,10 @@ public class TokenService {
|
||||||
}
|
}
|
||||||
|
|
||||||
String error = Validation.validateRegistrationForm(formData, requiredCredentialTypes);
|
String error = Validation.validateRegistrationForm(formData, requiredCredentialTypes);
|
||||||
|
if (error == null) {
|
||||||
|
error = Validation.validatePassword(formData, realm.getPasswordPolicy());
|
||||||
|
}
|
||||||
|
|
||||||
if (error != null) {
|
if (error != null) {
|
||||||
return Flows.forms(realm, request, uriInfo).setError(error).setFormData(formData)
|
return Flows.forms(realm, request, uriInfo).setError(error).setFormData(formData)
|
||||||
.setSocialRegistration(isSocialRegistration).forwardToRegistration();
|
.setSocialRegistration(isSocialRegistration).forwardToRegistration();
|
||||||
|
@ -464,7 +482,7 @@ public class TokenService {
|
||||||
@Path("login")
|
@Path("login")
|
||||||
@GET
|
@GET
|
||||||
public Response loginPage(final @QueryParam("response_type") String responseType,
|
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) {
|
final @QueryParam("scope") String scopeParam, final @QueryParam("state") String state, final @QueryParam("prompt") String prompt) {
|
||||||
logger.info("TokenService.loginPage");
|
logger.info("TokenService.loginPage");
|
||||||
OAuthFlows oauth = Flows.oauth(realm, request, uriInfo, authManager, tokenManager);
|
OAuthFlows oauth = Flows.oauth(realm, request, uriInfo, authManager, tokenManager);
|
||||||
|
@ -489,6 +507,11 @@ public class TokenService {
|
||||||
session.close();
|
session.close();
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
redirect = verifyRedirectUri(redirect, client);
|
||||||
|
if (redirect == null) {
|
||||||
|
return oauth.forwardToSecurityFailure("Invalid redirect_uri.");
|
||||||
|
}
|
||||||
|
|
||||||
logger.info("Checking roles...");
|
logger.info("Checking roles...");
|
||||||
RoleModel resourceRole = realm.getRole(Constants.APPLICATION_ROLE);
|
RoleModel resourceRole = realm.getRole(Constants.APPLICATION_ROLE);
|
||||||
RoleModel identityRequestRole = realm.getRole(Constants.IDENTITY_REQUESTER_ROLE);
|
RoleModel identityRequestRole = realm.getRole(Constants.IDENTITY_REQUESTER_ROLE);
|
||||||
|
@ -517,7 +540,7 @@ public class TokenService {
|
||||||
@Path("registrations")
|
@Path("registrations")
|
||||||
@GET
|
@GET
|
||||||
public Response registerPage(final @QueryParam("response_type") String responseType,
|
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) {
|
final @QueryParam("scope") String scopeParam, final @QueryParam("state") String state) {
|
||||||
logger.info("**********registerPage()");
|
logger.info("**********registerPage()");
|
||||||
OAuthFlows oauth = Flows.oauth(realm, request, uriInfo, authManager, tokenManager);
|
OAuthFlows oauth = Flows.oauth(realm, request, uriInfo, authManager, tokenManager);
|
||||||
|
@ -537,6 +560,11 @@ public class TokenService {
|
||||||
return oauth.forwardToSecurityFailure("Login requester not enabled.");
|
return oauth.forwardToSecurityFailure("Login requester not enabled.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
redirect = verifyRedirectUri(redirect, client);
|
||||||
|
if (redirect == null) {
|
||||||
|
return oauth.forwardToSecurityFailure("Invalid redirect_uri.");
|
||||||
|
}
|
||||||
|
|
||||||
if (!realm.isRegistrationAllowed()) {
|
if (!realm.isRegistrationAllowed()) {
|
||||||
logger.warn("Registration not allowed");
|
logger.warn("Registration not allowed");
|
||||||
return oauth.forwardToSecurityFailure("Registration not allowed");
|
return oauth.forwardToSecurityFailure("Registration not allowed");
|
||||||
|
@ -605,4 +633,15 @@ public class TokenService {
|
||||||
return location.build();
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.representations.idm.ApplicationRepresentation;
|
import org.keycloak.representations.idm.ApplicationRepresentation;
|
||||||
import org.keycloak.services.managers.ApplicationManager;
|
import org.keycloak.services.managers.ApplicationManager;
|
||||||
import org.keycloak.services.managers.RealmManager;
|
import org.keycloak.services.managers.RealmManager;
|
||||||
|
import org.keycloak.services.resources.flows.Flows;
|
||||||
|
|
||||||
import javax.ws.rs.Consumes;
|
import javax.ws.rs.Consumes;
|
||||||
import javax.ws.rs.GET;
|
import javax.ws.rs.GET;
|
||||||
|
@ -58,6 +59,9 @@ public class ApplicationsResource {
|
||||||
@POST
|
@POST
|
||||||
@Consumes(MediaType.APPLICATION_JSON)
|
@Consumes(MediaType.APPLICATION_JSON)
|
||||||
public Response createApplication(final @Context UriInfo uriInfo, final ApplicationRepresentation rep) {
|
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));
|
ApplicationManager resourceManager = new ApplicationManager(new RealmManager(session));
|
||||||
ApplicationModel applicationModel = resourceManager.createApplication(realm, rep);
|
ApplicationModel applicationModel = resourceManager.createApplication(realm, rep);
|
||||||
return Response.created(uriInfo.getAbsolutePathBuilder().path(applicationModel.getId()).build()).build();
|
return Response.created(uriInfo.getAbsolutePathBuilder().path(applicationModel.getId()).build()).build();
|
||||||
|
|
|
@ -8,6 +8,7 @@ import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.representations.idm.RealmRepresentation;
|
import org.keycloak.representations.idm.RealmRepresentation;
|
||||||
import org.keycloak.services.managers.RealmManager;
|
import org.keycloak.services.managers.RealmManager;
|
||||||
import org.keycloak.services.resources.SaasService;
|
import org.keycloak.services.resources.SaasService;
|
||||||
|
import org.keycloak.services.resources.flows.Flows;
|
||||||
|
|
||||||
import javax.ws.rs.*;
|
import javax.ws.rs.*;
|
||||||
import javax.ws.rs.container.ResourceContext;
|
import javax.ws.rs.container.ResourceContext;
|
||||||
|
@ -71,6 +72,10 @@ public class RealmsAdminResource {
|
||||||
public Response importRealm(@Context final UriInfo uriInfo, final RealmRepresentation rep) {
|
public Response importRealm(@Context final UriInfo uriInfo, final RealmRepresentation rep) {
|
||||||
logger.debug("importRealm: {0}", rep.getRealm());
|
logger.debug("importRealm: {0}", rep.getRealm());
|
||||||
RealmManager realmManager = new RealmManager(session);
|
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);
|
RealmModel realm = realmManager.importRealm(rep, admin);
|
||||||
URI location = realmUrl(uriInfo).build(realm.getId());
|
URI location = realmUrl(uriInfo).build(realm.getId());
|
||||||
logger.debug("imported realm success, sending back: {0}", location.toString());
|
logger.debug("imported realm success, sending back: {0}", location.toString());
|
||||||
|
|
|
@ -67,12 +67,6 @@ public class OAuthFlows {
|
||||||
}
|
}
|
||||||
|
|
||||||
public Response redirectAccessCode(AccessCodeEntry accessCode, String state, String redirect) {
|
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();
|
String code = accessCode.getCode();
|
||||||
UriBuilder redirectUri = UriBuilder.fromUri(redirect).queryParam("code", code);
|
UriBuilder redirectUri = UriBuilder.fromUri(redirect).queryParam("code", code);
|
||||||
log.debug("redirectAccessCode: state: {0}", state);
|
log.debug("redirectAccessCode: state: {0}", state);
|
||||||
|
@ -86,11 +80,6 @@ public class OAuthFlows {
|
||||||
}
|
}
|
||||||
|
|
||||||
public Response redirectError(UserModel client, String error, String state, String redirect) {
|
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);
|
UriBuilder redirectUri = UriBuilder.fromUri(redirect).queryParam("error", error);
|
||||||
if (state != null) {
|
if (state != null) {
|
||||||
redirectUri.queryParam("state", state);
|
redirectUri.queryParam("state", state);
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package org.keycloak.services.validation;
|
package org.keycloak.services.validation;
|
||||||
|
|
||||||
|
import org.keycloak.models.PasswordPolicy;
|
||||||
import org.keycloak.representations.idm.CredentialRepresentation;
|
import org.keycloak.representations.idm.CredentialRepresentation;
|
||||||
import org.keycloak.services.messages.Messages;
|
import org.keycloak.services.messages.Messages;
|
||||||
|
|
||||||
|
@ -38,6 +39,10 @@ public class Validation {
|
||||||
return null;
|
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) {
|
public static String validateUpdateProfileForm(MultivaluedMap<String, String> formData) {
|
||||||
if (isEmpty(formData.getFirst("firstName"))) {
|
if (isEmpty(formData.getFirst("firstName"))) {
|
||||||
return Messages.MISSING_FIRST_NAME;
|
return Messages.MISSING_FIRST_NAME;
|
||||||
|
|
|
@ -6,6 +6,7 @@ import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.KeycloakSessionFactory;
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
|
import org.keycloak.models.PasswordPolicy;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.RoleModel;
|
import org.keycloak.models.RoleModel;
|
||||||
import org.keycloak.representations.idm.RealmRepresentation;
|
import org.keycloak.representations.idm.RealmRepresentation;
|
||||||
|
@ -46,6 +47,7 @@ public class ModelTest extends AbstractKeycloakServerTest {
|
||||||
realm.setSslNotRequired(true);
|
realm.setSslNotRequired(true);
|
||||||
realm.setVerifyEmail(true);
|
realm.setVerifyEmail(true);
|
||||||
realm.setTokenLifespan(1000);
|
realm.setTokenLifespan(1000);
|
||||||
|
realm.setPasswordPolicy(new PasswordPolicy("length"));
|
||||||
realm.setAccessCodeLifespan(1001);
|
realm.setAccessCodeLifespan(1001);
|
||||||
realm.setAccessCodeLifespanUserAction(1002);
|
realm.setAccessCodeLifespanUserAction(1002);
|
||||||
realm.setPublicKeyPem("0234234");
|
realm.setPublicKeyPem("0234234");
|
||||||
|
|
|
@ -93,7 +93,7 @@ public class OAuthClient {
|
||||||
|
|
||||||
driver.findElement(By.id("username")).sendKeys(username);
|
driver.findElement(By.id("username")).sendKeys(username);
|
||||||
driver.findElement(By.id("password")).sendKeys(password);
|
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);
|
return new AuthorizationCodeResponse(this);
|
||||||
}
|
}
|
||||||
|
@ -103,7 +103,7 @@ public class OAuthClient {
|
||||||
|
|
||||||
driver.findElement(By.id("username")).sendKeys(username);
|
driver.findElement(By.id("username")).sendKeys(username);
|
||||||
driver.findElement(By.id("password")).sendKeys(password);
|
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) {
|
public AccessTokenResponse doAccessTokenRequest(String code, String password) {
|
||||||
|
|
|
@ -35,6 +35,7 @@ import org.keycloak.testsuite.pages.AppPage;
|
||||||
import org.keycloak.testsuite.pages.AppPage.RequestType;
|
import org.keycloak.testsuite.pages.AppPage.RequestType;
|
||||||
import org.keycloak.testsuite.pages.LoginConfigTotpPage;
|
import org.keycloak.testsuite.pages.LoginConfigTotpPage;
|
||||||
import org.keycloak.testsuite.pages.LoginPage;
|
import org.keycloak.testsuite.pages.LoginPage;
|
||||||
|
import org.keycloak.testsuite.pages.LoginTotpPage;
|
||||||
import org.keycloak.testsuite.pages.RegisterPage;
|
import org.keycloak.testsuite.pages.RegisterPage;
|
||||||
import org.keycloak.testsuite.rule.KeycloakRule;
|
import org.keycloak.testsuite.rule.KeycloakRule;
|
||||||
import org.keycloak.testsuite.rule.KeycloakRule.KeycloakSetup;
|
import org.keycloak.testsuite.rule.KeycloakRule.KeycloakSetup;
|
||||||
|
@ -70,6 +71,9 @@ public class RequiredActionTotpSetupTest {
|
||||||
@WebResource
|
@WebResource
|
||||||
protected LoginPage loginPage;
|
protected LoginPage loginPage;
|
||||||
|
|
||||||
|
@WebResource
|
||||||
|
protected LoginTotpPage loginTotpPage;
|
||||||
|
|
||||||
@WebResource
|
@WebResource
|
||||||
protected LoginConfigTotpPage totpPage;
|
protected LoginConfigTotpPage totpPage;
|
||||||
|
|
||||||
|
@ -113,7 +117,8 @@ public class RequiredActionTotpSetupTest {
|
||||||
oauth.openLogout();
|
oauth.openLogout();
|
||||||
|
|
||||||
loginPage.open();
|
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());
|
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||||
}
|
}
|
||||||
|
@ -146,7 +151,7 @@ public class RequiredActionTotpSetupTest {
|
||||||
Assert.assertFalse(totpPage.isCurrent());
|
Assert.assertFalse(totpPage.isCurrent());
|
||||||
|
|
||||||
// Login with one-time password
|
// Login with one-time password
|
||||||
loginPage.loginTotp("setupTotp2", "password2", totp.generate(totpCode));
|
loginTotpPage.login(totp.generate(totpCode));
|
||||||
|
|
||||||
// Open account page
|
// Open account page
|
||||||
accountTotpPage.open();
|
accountTotpPage.open();
|
||||||
|
|
|
@ -163,6 +163,36 @@ public class AccountTest {
|
||||||
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@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
|
@Test
|
||||||
public void changeProfile() {
|
public void changeProfile() {
|
||||||
profilePage.open();
|
profilePage.open();
|
||||||
|
|
|
@ -25,6 +25,7 @@ import org.junit.Assert;
|
||||||
import org.junit.ClassRule;
|
import org.junit.ClassRule;
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
import org.keycloak.testsuite.OAuthClient;
|
||||||
import org.keycloak.testsuite.pages.AppPage;
|
import org.keycloak.testsuite.pages.AppPage;
|
||||||
import org.keycloak.testsuite.pages.AppPage.RequestType;
|
import org.keycloak.testsuite.pages.AppPage.RequestType;
|
||||||
import org.keycloak.testsuite.pages.LoginPage;
|
import org.keycloak.testsuite.pages.LoginPage;
|
||||||
|
@ -44,6 +45,10 @@ public class LoginTest {
|
||||||
@Rule
|
@Rule
|
||||||
public WebRule webRule = new WebRule(this);
|
public WebRule webRule = new WebRule(this);
|
||||||
|
|
||||||
|
@WebResource
|
||||||
|
protected OAuthClient oauth;
|
||||||
|
|
||||||
|
|
||||||
@WebResource
|
@WebResource
|
||||||
protected WebDriver driver;
|
protected WebDriver driver;
|
||||||
|
|
||||||
|
@ -79,6 +84,17 @@ public class LoginTest {
|
||||||
loginPage.login("test-user@localhost", "password");
|
loginPage.login("test-user@localhost", "password");
|
||||||
|
|
||||||
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
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"));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,9 @@ import org.junit.Assert;
|
||||||
import org.junit.ClassRule;
|
import org.junit.ClassRule;
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
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;
|
||||||
import org.keycloak.testsuite.pages.AppPage.RequestType;
|
import org.keycloak.testsuite.pages.AppPage.RequestType;
|
||||||
import org.keycloak.testsuite.pages.LoginPage;
|
import org.keycloak.testsuite.pages.LoginPage;
|
||||||
|
@ -93,6 +96,37 @@ public class RegisterTest {
|
||||||
Assert.assertEquals("Please specify password.", registerPage.getError());
|
Assert.assertEquals("Please specify password.", registerPage.getError());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@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
|
@Test
|
||||||
public void registerUserMissingUsername() {
|
public void registerUserMissingUsername() {
|
||||||
loginPage.open();
|
loginPage.open();
|
||||||
|
|
|
@ -97,31 +97,6 @@ public class AuthorizationCodeTest {
|
||||||
Assert.assertNotNull(response.getCode());
|
Assert.assertNotNull(response.getCode());
|
||||||
}
|
}
|
||||||
|
|
||||||
@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
|
@Test
|
||||||
public void authorizationRequestNoState() throws IOException {
|
public void authorizationRequestNoState() throws IOException {
|
||||||
AuthorizationCodeResponse response = oauth.doLogin("test-user@localhost", "password");
|
AuthorizationCodeResponse response = oauth.doLogin("test-user@localhost", "password");
|
||||||
|
|
|
@ -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="));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -44,9 +44,12 @@ public class LoginPage extends AbstractPage {
|
||||||
@FindBy(id = "totp")
|
@FindBy(id = "totp")
|
||||||
private WebElement totp;
|
private WebElement totp;
|
||||||
|
|
||||||
@FindBy(css = "input[type=\"submit\"]")
|
@FindBy(name = "login")
|
||||||
private WebElement submitButton;
|
private WebElement submitButton;
|
||||||
|
|
||||||
|
@FindBy(name = "cancel")
|
||||||
|
private WebElement cancelButton;
|
||||||
|
|
||||||
@FindBy(linkText = "Register")
|
@FindBy(linkText = "Register")
|
||||||
private WebElement registerLink;
|
private WebElement registerLink;
|
||||||
|
|
||||||
|
@ -66,17 +69,8 @@ public class LoginPage extends AbstractPage {
|
||||||
submitButton.click();
|
submitButton.click();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void loginTotp(String username, String password, String code) {
|
public void cancel() {
|
||||||
usernameInput.clear();
|
cancelButton.click();
|
||||||
usernameInput.sendKeys(username);
|
|
||||||
|
|
||||||
passwordInput.clear();
|
|
||||||
passwordInput.sendKeys(password);
|
|
||||||
|
|
||||||
totp.clear();
|
|
||||||
totp.sendKeys(code);
|
|
||||||
|
|
||||||
submitButton.click();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getError() {
|
public String getError() {
|
||||||
|
|
|
@ -54,26 +54,32 @@ public class RegisterPage extends AbstractPage {
|
||||||
private WebElement loginErrorMessage;
|
private WebElement loginErrorMessage;
|
||||||
|
|
||||||
public void register(String firstName, String lastName, String email, String username, String password, String passwordConfirm) {
|
public void register(String firstName, String lastName, String email, String username, String password, String passwordConfirm) {
|
||||||
|
firstNameInput.clear();
|
||||||
if (firstName != null) {
|
if (firstName != null) {
|
||||||
firstNameInput.sendKeys(firstName);
|
firstNameInput.sendKeys(firstName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lastNameInput.clear();
|
||||||
if (lastName != null) {
|
if (lastName != null) {
|
||||||
lastNameInput.sendKeys(lastName);
|
lastNameInput.sendKeys(lastName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
emailInput.clear();
|
||||||
if (email != null) {
|
if (email != null) {
|
||||||
emailInput.sendKeys(email);
|
emailInput.sendKeys(email);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
usernameInput.clear();
|
||||||
if (username != null) {
|
if (username != null) {
|
||||||
usernameInput.sendKeys(username);
|
usernameInput.sendKeys(username);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
passwordInput.clear();
|
||||||
if (password != null) {
|
if (password != null) {
|
||||||
passwordInput.sendKeys(password);
|
passwordInput.sendKeys(password);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
passwordConfirmInput.clear();
|
||||||
if (passwordConfirm != null) {
|
if (passwordConfirm != null) {
|
||||||
passwordConfirmInput.sendKeys(passwordConfirm);
|
passwordConfirmInput.sendKeys(passwordConfirm);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue