This commit is contained in:
Bill Burke 2013-11-15 14:18:00 -05:00
commit 934eca73f4
120 changed files with 1173 additions and 652 deletions

View file

@ -55,17 +55,44 @@ body {
color: #fff;
font-weight: bold;
}
.loading span {
background: url(img/loader.gif) no-repeat center top;
.loading-backdrop {
position: fixed;
z-index: 1000;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 1031;
background-color: #FFFFFF;
opacity: 0.75;
}
.loading {
position: fixed;
z-index: 1032;
top: 50%;
left: 50%;
width: 6em;
height: 6em;
margin-top: -3em;
margin-left: -3em;
text-align: center;
}
.loading img {
width: 3em;
height: 3em;
background-color: #f0f0f0;
display: inline-block;
padding: 0.3em;
border-radius: 0.4em;
}
.loading span {
background: url(img/loader.gif) no-repeat center top;
font-size: 1.2em;
color: #666;
display: inline-block;
padding-top: 0.36363636363636em;
margin-top: -2.27272727272727em;
margin-left: -2.27272727272727em;
padding-top: 2.90909090909091em;
font-size: 1.1em;
color: #666;
}
/* Header */
.header.rcue {
@ -485,11 +512,11 @@ td.token-cell button {
font-family: "Open Sans", sans-serif;
margin: 0;
}
.modal .modal-body p {
.modal .modal-body p span {
display: block;
font-size: 1.1em;
}
.modal .modal-body p.primary {
font-size: 1.1em;
.modal .modal-body p span.primary {
font-weight: bold;
margin-bottom: 0.45454545454545em;
}

View file

@ -74,19 +74,46 @@ body {
}
}
.loading-backdrop {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 1031;
background-color: #FFFFFF;
opacity: 0.75;
}
.loading {
position: fixed;
z-index: 1032;
top: 50%;
left: 50%;
width: 6em;
height: 6em;
margin-top: -3em;
margin-left: -3em;
text-align: center;
img {
width: 3em;
height: 3em;
background-color: #f0f0f0;
display: inline-block;
padding: 0.3em;
border-radius: 0.4em;
}
span {
background: url(img/loader.gif) no-repeat center top;
position: fixed;
z-index: 1000;
top: 50%;
left: 50%;
font-size: 1.2em;
color: #666;
display: inline-block;
padding-top: 0.36363636363636em;
margin-top: -2.27272727272727em;
margin-left: -2.27272727272727em;
padding-top: 2.90909090909091em;
font-size: 1.1em;
color: #666;
}
}
@ -592,11 +619,11 @@ td.token-cell button {
.modal-body {
p {
p span {
display: block;
font-size: 1.1em;
&.primary {
font-size: 1.1em;
font-weight: bold;
margin-bottom: 0.45454545454545em;
}

View file

@ -73,8 +73,10 @@
</button>
</div>
<div id="loading">
<i class="icon-spinner icon-spin"></i> Loading...
<div id="loading" class="loading-backdrop">
<div class="loading">
<span>Loading...</span>
</div>
</div>
</div>

View file

@ -2,6 +2,7 @@
var module = angular.module('keycloak', [ 'keycloak.services', 'keycloak.loaders', 'keycloak.controllers', 'ui.bootstrap', 'ui.select2' ]);
var resourceRequests = 0;
var loadingTimer = -1;
module.config([ '$routeProvider', function($routeProvider) {
@ -46,6 +47,21 @@ module.config([ '$routeProvider', function($routeProvider) {
},
controller : 'RealmSocialCtrl'
})
.when('/realms/:realm/registration-settings', {
templateUrl : 'partials/realm-registration.html',
resolve : {
realm : function(RealmLoader) {
return RealmLoader();
},
applications : function(ApplicationListLoader) {
return ApplicationListLoader();
},
roles : function(RoleListLoader) {
return RoleListLoader();
}
},
controller : 'RealmRegistrationCtrl'
})
.when('/realms/:realm/required-credentials', {
templateUrl : 'partials/realm-credentials.html',
resolve : {
@ -281,7 +297,10 @@ module.config(function($httpProvider) {
var spinnerFunction = function(data, headersGetter) {
if (resourceRequests == 0) {
loadingTimer = window.setTimeout(function() {
$('#loading').show();
loadingTimer = -1;
}, 500);
}
resourceRequests++;
return data;
@ -301,7 +320,7 @@ module.factory('errorInterceptor', function($q, $window, $rootScope, $location,
if (response.status == 401) {
console.log('session timeout?');
Auth.loggedIn = false;
$location.url('/');
window.location = '/auth-server/rest/saas/login?path=' + $location.path();
} else {
$rootScope.httpProviderError = response.status;
}
@ -315,12 +334,20 @@ module.factory('spinnerInterceptor', function($q, $window, $rootScope, $location
return promise.then(function(response) {
resourceRequests--;
if (resourceRequests == 0) {
if(loadingTimer != -1) {
window.clearTimeout(loadingTimer);
loadingTimer = -1;
}
$('#loading').hide();
}
return response;
}, function(response) {
resourceRequests--;
if (resourceRequests == 0) {
if(loadingTimer != -1) {
window.clearTimeout(loadingTimer);
loadingTimer = -1;
}
$('#loading').hide();
}
@ -368,10 +395,42 @@ module.directive('collapsed', function() {
}
});
/**
* Directive for presenting an ON-OFF switch for checkbox.
* Usage: <input ng-model="mmm" name="nnn" id="iii" onoffswitch [on-text="ooo" off-text="fff"] />
*/
module.directive('onoffswitch', function() {
return {
restrict: "EA",
require: 'ngModel',
replace: true,
scope: {
ngModel: '=',
ngBind: '=',
name: '=',
id: '=',
onText: '@onText',
offText: '@offText'
},
compile: function(element, attrs) {
if (!attrs.onText) { attrs.onText = "ON"; }
if (!attrs.offText) { attrs.offText = "OFF"; }
var html = "<div class=\"onoffswitch\">" +
"<input type=\"checkbox\" data-ng-model=\"ngModel\" class=\"onoffswitch-checkbox\" name=\"" + attrs.name + "\" id=\"" + attrs.id + "\">" +
"<label for=\"" + attrs.id + "\" class=\"onoffswitch-label\">" +
"<span class=\"onoffswitch-inner\">" +
"<span class=\"onoffswitch-active\">{{onText}}</span>" +
"<span class=\"onoffswitch-inactive\">{{offText}}</span>" +
"</span>" +
"<span class=\"onoffswitch-switch\"></span>" +
"</label>" +
"</div>";
element.replaceWith($(html));
}
}
});
module.directive('kcInput', function() {
@ -430,6 +489,7 @@ module.filter('remove', function() {
for ( var i = 0; i < input.length; i++) {
var e = input[i];
if (Array.isArray(remove)) {
for (var j = 0; j < remove.length; j++) {
if (attribute) {
if (remove[j][attribute] == e[attribute]) {
@ -443,6 +503,17 @@ module.filter('remove', function() {
}
}
}
} else {
if (attribute) {
if (remove[attribute] == e[attribute]) {
e = null;
}
} else {
if (remove == e) {
e = null;
}
}
}
if (e != null) {
out.push(e);

View file

@ -20,4 +20,3 @@ function randomString(len) {
}
return randomString;
}

View file

@ -15,6 +15,7 @@ module.controller('GlobalCtrl', function($scope, $http, Auth, Current, $location
$scope.$watch(function() {
return $location.path();
}, function() {
$scope.fragment = $location.path();
$scope.path = $location.path().substring(1).split("/");
});
@ -73,6 +74,9 @@ module.controller('RealmDetailCtrl', function($scope, Current, Realm, realm, $ht
$scope.realm.requireSsl = !realm.sslNotRequired;
}
$scope.social = $scope.realm.social;
$scope.registrationAllowed = $scope.realm.registrationAllowed;
var oldCopy = angular.copy($scope.realm);
@ -104,6 +108,8 @@ module.controller('RealmDetailCtrl', function($scope, Current, Realm, realm, $ht
}
$location.url("/realms/" + id);
Notifications.success("The realm has been created.");
$scope.social = $scope.realm.social;
$scope.registrationAllowed = $scope.realm.registrationAllowed;
});
});
} else {
@ -122,6 +128,8 @@ module.controller('RealmDetailCtrl', function($scope, Current, Realm, realm, $ht
});
$location.url("/realms/" + id);
Notifications.success("Your changes have been saved to the realm.");
$scope.social = $scope.realm.social;
$scope.registrationAllowed = $scope.realm.registrationAllowed;
});
}
} else {
@ -158,7 +166,8 @@ module.controller('RealmRequiredCredentialsCtrl', function($scope, Realm, realm,
id : realm.id, realm : realm.realm, social : realm.social,
requiredCredentials : realm.requiredCredentials,
requiredApplicationCredentials : realm.requiredApplicationCredentials,
requiredOAuthClientCredentials : realm.requiredOAuthClientCredentials
requiredOAuthClientCredentials : realm.requiredOAuthClientCredentials,
registrationAllowed : realm.registrationAllowed
};
$scope.userCredentialOptions = {
@ -196,10 +205,156 @@ module.controller('RealmRequiredCredentialsCtrl', function($scope, Realm, realm,
};
});
module.controller('RealmRegistrationCtrl', function ($scope, Realm, realm, applications, roles, Notifications, ApplicationRole, Application) {
console.log('RealmRegistrationCtrl');
$scope.realm = realm;
$scope.availableRealmRoles = [];
$scope.selectedRealmRoles = [];
$scope.selectedRealmDefRoles = [];
$scope.applications = angular.copy(applications);
$scope.availableAppRoles = [];
$scope.selectedAppRoles = [];
$scope.selectedAppDefRoles = [];
if (!$scope.realm.hasOwnProperty('defaultRoles') || $scope.realm.defaultRoles === null) {
$scope.realm.defaultRoles = [];
}
// Populate available roles. Available roles are neither already assigned or system roles.
for (var i = 0; i < roles.length; i++) {
var item = roles[i].name;
if ($scope.realm.defaultRoles.indexOf(item) < 0) {
$scope.availableRealmRoles.push(item);
}
}
$scope.addRealmDefaultRole = function () {
// Remove selected roles from the Available roles and add them to realm default roles (move from left to right).
for (var i = 0; i < $scope.selectedRealmRoles.length; i++) {
var selectedRole = $scope.selectedRealmRoles[i];
$scope.realm.defaultRoles.push(selectedRole);
var index = $scope.availableRealmRoles.indexOf(selectedRole);
if (index > -1) {
$scope.availableRealmRoles.splice(index, 1);
}
}
// Update/save the realm with new default roles.
Realm.update($scope.realm, function () {
Notifications.success("Realm default roles updated.");
});
};
$scope.deleteRealmDefaultRole = function () {
// Remove selected roles from the realm default roles and add them to available roles (move from right to left).
for (var i = 0; i < $scope.selectedRealmDefRoles.length; i++) {
$scope.availableRealmRoles.push($scope.selectedRealmDefRoles[i]);
var index = $scope.realm.defaultRoles.indexOf($scope.selectedRealmDefRoles[i]);
if (index > -1) {
$scope.realm.defaultRoles.splice(index, 1);
}
}
// Update/save the realm with new default roles.
//var realmCopy = angular.copy($scope.realm);
Realm.update($scope.realm, function () {
Notifications.success("Realm default roles updated.");
});
};
$scope.changeApplication = function () {
$scope.selectedAppRoles = [];
$scope.selectedAppDefRoles = [];
// Populate available roles for selected application
var appDefaultRoles = ApplicationRole.query({realm: $scope.realm.id, application: $scope.application.id}, function () {
if (!$scope.application.hasOwnProperty('defaultRoles') || $scope.application.defaultRoles === null) {
$scope.application.defaultRoles = [];
}
$scope.availableAppRoles = [];
for (var i = 0; i < appDefaultRoles.length; i++) {
var roleName = appDefaultRoles[i].name;
if (systemRoles.indexOf(roleName) < 0 && $scope.application.defaultRoles.indexOf(roleName) < 0) {
$scope.availableAppRoles.push(roleName);
}
}
});
};
$scope.addAppDefaultRole = function () {
// Remove selected roles from the app available roles and add them to app default roles (move from left to right).
for (var i = 0; i < $scope.selectedAppRoles.length; i++) {
var role = $scope.selectedAppRoles[i];
var idx = $scope.application.defaultRoles.indexOf(role);
if (idx < 0) {
$scope.application.defaultRoles.push(role);
}
idx = $scope.availableAppRoles.indexOf(role);
if (idx != -1) {
$scope.availableAppRoles.splice(idx, 1);
}
}
// Update/save the selected application with new default roles.
Application.update({
realm: $scope.realm.id,
id: $scope.application.id
}, $scope.application, function () {
Notifications.success("Your changes have been saved to the application.");
});
};
$scope.rmAppDefaultRole = function () {
// Remove selected roles from the app default roles and add them to app available roles (move from right to left).
for (var i = 0; i < $scope.selectedAppDefRoles.length; i++) {
var role = $scope.selectedAppDefRoles[i];
var idx = $scope.application.defaultRoles.indexOf(role);
if (idx != -1) {
$scope.application.defaultRoles.splice(idx, 1);
}
idx = $scope.availableAppRoles.indexOf(role);
if (idx < 0) {
$scope.availableAppRoles.push(role);
}
}
// Update/save the selected application with new default roles.
Application.update({
realm: $scope.realm.id,
id: $scope.application.id
}, $scope.application, function () {
Notifications.success("Your changes have been saved to the application.");
});
};
});
module.controller('RealmSocialCtrl', function($scope, realm, Realm, $location, Notifications) {
console.log('RealmSocialCtrl');
$scope.realm = { id : realm.id, realm : realm.realm, social : realm.social, tokenLifespan : realm.tokenLifespan, accessCodeLifespan : realm.accessCodeLifespan };
$scope.realm = { id : realm.id, realm : realm.realm, social : realm.social, registrationAllowed : realm.registrationAllowed,
tokenLifespan : realm.tokenLifespan, accessCodeLifespan : realm.accessCodeLifespan };
if (!realm["socialProviders"]){
$scope.realm["socialProviders"] = {};
@ -207,8 +362,13 @@ module.controller('RealmSocialCtrl', function($scope, realm, Realm, $location, N
$scope.realm["socialProviders"] = realm.socialProviders;
}
// Hardcoded provider list
$scope.availableProviders = [ "google", "facebook", "twitter"];
// Hardcoded provider list in form of map providerId:providerName
$scope.allProviders = { google:"Google", facebook:"Facebook", twitter:"Twitter" };
$scope.availableProviders = [];
for (var provider in $scope.allProviders){
$scope.availableProviders.push(provider);
}
var oldCopy = angular.copy($scope.realm);
$scope.changed = false;
@ -226,6 +386,9 @@ module.controller('RealmSocialCtrl', function($scope, realm, Realm, $location, N
// Fill in configured providers
var initSocial = function() {
// postSaveProviders is used for remembering providers which were already validated after pressing the save button
// thanks to this it's easy to distinguish between newly added fields and those already tried to be saved
$scope.postSaveProviders = [];
$scope.unsetProviders = [];
$scope.configuredProviders = [];
@ -241,7 +404,7 @@ module.controller('RealmSocialCtrl', function($scope, realm, Realm, $location, N
// If no providers are already configured, you can add any of them
if ($scope.configuredProviders.length == 0){
$scope.unsetProviders = $scope.availableProviders;
$scope.unsetProviders = $scope.availableProviders.slice(0);
} else {
for (var i = 0; i < $scope.availableProviders.length; i++){
var providerId = $scope.availableProviders[i];
@ -270,6 +433,13 @@ module.controller('RealmSocialCtrl', function($scope, realm, Realm, $location, N
delete $scope.realm.socialProviders[pId+".key"];
delete $scope.realm.socialProviders[pId+".secret"];
$scope.configuredProviders.remove($scope.configuredProviders.indexOf(pId));
// Removing from postSaveProviders, so the empty fields are not red if the provider is added to the list again
var rId = $scope.postSaveProviders.indexOf(pId);
if (rId > -1){
$scope.postSaveProviders.remove(rId)
}
$scope.unsetProviders.push(pId);
};
@ -280,8 +450,6 @@ module.controller('RealmSocialCtrl', function($scope, realm, Realm, $location, N
}, true);
$scope.save = function() {
$scope.saveClicked = true;
if ($scope.realmForm.$valid) {
var realmCopy = angular.copy($scope.realm);
realmCopy.social = true;
@ -289,10 +457,12 @@ module.controller('RealmSocialCtrl', function($scope, realm, Realm, $location, N
Realm.update(realmCopy, function () {
$location.url("/realms/" + realm.id + "/social-settings");
Notifications.success("Saved changes to realm");
oldCopy = realmCopy;
});
} else {
$scope.realmForm.showErrors = true;
Notifications.error("Some required fields are missing values.");
$scope.postSaveProviders = $scope.configuredProviders.slice(0);
}
};
@ -317,7 +487,10 @@ module.controller('RealmSocialCtrl', function($scope, realm, Realm, $location, N
module.controller('RealmTokenDetailCtrl', function($scope, Realm, realm, $http, $location, Dialog, Notifications) {
console.log('RealmTokenDetailCtrl');
$scope.realm = { id : realm.id, realm : realm.realm, social : realm.social, tokenLifespan : realm.tokenLifespan, accessCodeLifespan : realm.accessCodeLifespan , accessCodeLifespanUserAction : realm.accessCodeLifespanUserAction };
$scope.realm = { id : realm.id, realm : realm.realm, social : realm.social, registrationAllowed : realm.registrationAllowed,
tokenLifespan : realm.tokenLifespan, accessCodeLifespan : realm.accessCodeLifespan,
accessCodeLifespanUserAction : realm.accessCodeLifespanUserAction };
$scope.realm.tokenLifespanUnit = 'Seconds';
$scope.realm.accessCodeLifespanUnit = 'Seconds';
$scope.realm.accessCodeLifespanUserActionUnit = 'Seconds';
@ -376,6 +549,7 @@ module.controller('RealmTokenDetailCtrl', function($scope, Realm, realm, $http,
});
module.controller('RoleListCtrl', function($scope, $location, realm, roles) {
$scope.realm = realm;
$scope.roles = roles;
@ -456,7 +630,7 @@ module.controller('RoleDetailCtrl', function($scope, realm, role, Role, $locatio
module.controller('RealmSMTPSettingsCtrl', function($scope, Current, Realm, realm, $http, $location, Dialog, Notifications) {
$scope.realm = {
id : realm.id, realm : realm.realm, social : realm.social,
id : realm.id, realm : realm.realm, social : realm.social, registrationAllowed : realm.registrationAllowed,
smtpServer: realm.smtpServer
};

View file

@ -17,8 +17,8 @@ module.service('Dialog', function($dialog) {
var dialog = {};
dialog.confirmDelete = function(name, type, success) {
var title = 'Delete ' + type.charAt(0).toUpperCase() + type.slice(1);
var msg = '<p class="primary">Are you sure you want to permanently delete the ' + type + ' "' + name + '"?</p>' +
'<p>This action can\'t be undone.</p>';
var msg = '<span class="primary">Are you sure you want to permanently delete the ' + type + ' "' + name + '"?</span>' +
'<span>This action can\'t be undone.</span>';
var btns = [ {
result : 'cancel',
label : 'Cancel'

View file

@ -44,17 +44,7 @@
<div class="form-group clearfix block">
<label for="enabled" class="control-label">Enabled</label>
<div class="onoffswitch">
<input type="checkbox" data-ng-model="application.enabled" class="onoffswitch-checkbox"
name="enabled" id="enabled">
<label for="enabled" class="onoffswitch-label">
<span class="onoffswitch-inner">
<span class="onoffswitch-active">ON</span>
<span class="onoffswitch-inactive">OFF</span>
</span>
<span class="onoffswitch-switch"></span>
</label>
</div>
<input ng-model="application.enabled" name="enabled" id="enabled" onoffswitch />
</div>
<div class="form-group">
<label for="baseUrl" class="control-label">Base URL</label>

View file

@ -56,7 +56,7 @@
<label for="applications">Application</label>
<div class="input-group">
<div class="select-rcue">
<select id="applications" name="applications" ng-change="changeApplication()" ng-model="targetApp" ng-options="a.name for a in applications">
<select id="applications" name="applications" ng-change="changeApplication()" ng-model="targetApp" ng-options="a.name for a in (applications|remove:application:'id')">
<option value="" selected> Select an Application </option>
</select>
</div>

View file

@ -3,7 +3,7 @@
<div class="navbar-inner clearfix container">
<h1><a href="#/realms/{{realm.id}}"><strong>Keycloak</strong> Central Login</a></h1>
<ul class="nav pull-right" data-ng-hide="auth.loggedIn">
<li><a href="/auth-server/rest/saas/login">Login</a></li>
<li><a href="/auth-server/rest/saas/login?path={{fragment}}">Login</a></li>
<li><a href="/auth-server/rest/saas/registrations">Register</a></li>
</ul>
<ul class="nav pull-right" data-ng-show="auth.loggedIn">
@ -25,7 +25,7 @@
<span class="dropdown-label" data-ng-show="showNav()">Realm:</span>
<div class="select-rcue" data-ng-show="showNav()">
<select ng-change="changeRealm()" ng-model="current.realm" ng-options="r.realm for r in current.realms"></select>
</div><a href="#/realms/{{realm.id}}" id="refresh" data-ng-show="showNav()"><span class="icon-spinner6">Icon: spinner</span></a>
</div><a href="#/realms/{{current.realm.id}}" id="refresh" data-ng-show="showNav()"><span class="icon-spinner6">Icon: spinner</span></a>
</li>
</ul>
<div class="pull-right" data-ng-show="auth.loggedIn">

View file

@ -6,6 +6,7 @@
<ul class="rcue-tabs">
<li><a href="#/realms/{{realm.id}}">General</a></li>
<li data-ng-show="realm.social"><a href="#/realms/{{realm.id}}/social-settings">Social</a></li>
<li data-ng-show="realm.registrationAllowed"><a href="#/realms/{{realm.id}}/registration-settings">Registration</a></li>
<li><a href="#/realms/{{realm.id}}/roles">Roles</a></li>
<li class="active"><a href="#/realms/{{realm.id}}/required-credentials">Credentials</a></li>
<li><a href="#/realms/{{realm.id}}/token-settings">Token</a></li>

View file

@ -5,7 +5,8 @@
<div class="top-nav" data-ng-hide="createRealm">
<ul class="rcue-tabs">
<li class="active"><a href="#/realms/{{realm.id}}">General</a></li>
<li data-ng-show="realm.social"><a href="#/realms/{{realm.id}}/social-settings">Social</a></li>
<li data-ng-show="social"><a href="#/realms/{{realm.id}}/social-settings">Social</a></li>
<li data-ng-show="registrationAllowed"><a href="#/realms/{{realm.id}}/registration-settings">Registration</a></li>
<li><a href="#/realms/{{realm.id}}/roles">Roles</a></li>
<li><a href="#/realms/{{realm.id}}/required-credentials">Credentials</a></li>
<li><a href="#/realms/{{realm.id}}/token-settings">Token</a></li>
@ -34,115 +35,41 @@
</div>
<div class="form-group">
<label for="enabled" class="control-label">Enabled</label>
<div class="onoffswitch">
<input type="checkbox" data-ng-model="realm.enabled" class="onoffswitch-checkbox"
name="enabled" id="enabled">
<label for="enabled" class="onoffswitch-label">
<span class="onoffswitch-inner">
<span class="onoffswitch-active">ON</span>
<span class="onoffswitch-inactive">OFF</span>
</span>
<span class="onoffswitch-switch"></span>
</label>
</div>
<input ng-model="realm.enabled" name="enabled" id="enabled" onoffswitch />
</div>
</fieldset>
<fieldset>
<legend uncollapsed><span class="text">Login Options</span></legend>
<div class="form-group clearfix block">
<label for="social" class="control-label">Social login</label>
<div class="onoffswitch">
<input type="checkbox" data-ng-model="realm.social" class="onoffswitch-checkbox" name="social" id="social">
<label for="social" class="onoffswitch-label">
<span class="onoffswitch-inner">
<span class="onoffswitch-active">ON</span>
<span class="onoffswitch-inactive">OFF</span>
</span>
<span class="onoffswitch-switch"></span>
</label>
</div>
<input ng-model="realm.social" name="social" id="social" onoffswitch />
</div>
<div class="form-group clearfix block">
<label for="registrationAllowed" class="control-label">User registration</label>
<div class="onoffswitch">
<input type="checkbox" data-ng-model="realm.registrationAllowed" class="onoffswitch-checkbox" name="registrationAllowed" id="registrationAllowed">
<label for="registrationAllowed" class="onoffswitch-label">
<span class="onoffswitch-inner">
<span class="onoffswitch-active">ON</span>
<span class="onoffswitch-inactive">OFF</span>
</span>
<span class="onoffswitch-switch"></span>
</label>
</div>
<input ng-model="realm.registrationAllowed" name="registrationAllowed" id="registrationAllowed" onoffswitch />
</div>
<div class="form-group clearfix block">
<label for="resetPasswordAllowed" class="control-label">Reset password</label>
<div class="onoffswitch">
<input type="checkbox" data-ng-model="realm.resetPasswordAllowed" class="onoffswitch-checkbox" name="resetPasswordAllowed" id="resetPasswordAllowed">
<label for="resetPasswordAllowed" class="onoffswitch-label">
<span class="onoffswitch-inner">
<span class="onoffswitch-active">ON</span>
<span class="onoffswitch-inactive">OFF</span>
</span>
<span class="onoffswitch-switch"></span>
</label>
</div>
<input ng-model="realm.resetPasswordAllowed" name="resetPasswordAllowed" id="resetPasswordAllowed" onoffswitch />
</div>
<div class="form-group clearfix block">
<label for="verifyEmail" class="control-label">Verify email</label>
<div class="onoffswitch">
<input type="checkbox" data-ng-model="realm.verifyEmail" class="onoffswitch-checkbox" name="verifyEmail" id="verifyEmail">
<label for="verifyEmail" class="onoffswitch-label">
<span class="onoffswitch-inner">
<span class="onoffswitch-active">ON</span>
<span class="onoffswitch-inactive">OFF</span>
</span>
<span class="onoffswitch-switch"></span>
</label>
</div>
<input ng-model="realm.verifyEmail" name="verifyEmail" id="verifyEmail" onoffswitch />
</div>
<div class="form-group clearfix block">
<label for="accountManagement" class="control-label two-lines">User account management</label>
<div class="onoffswitch">
<input type="checkbox" data-ng-model="realm.accountManagement" class="onoffswitch-checkbox"
name="accountManagement" id="accountManagement">
<label for="accountManagement" class="onoffswitch-label">
<span class="onoffswitch-inner">
<span class="onoffswitch-active">ON</span>
<span class="onoffswitch-inactive">OFF</span>
</span>
<span class="onoffswitch-switch"></span>
</label>
</div>
<input ng-model="realm.accountManagement" name="accountManagement" id="accountManagement" onoffswitch />
</div>
<div class="form-group clearfix block">
<label for="requireSsl" class="control-label">Require SSL</label>
<div class="onoffswitch">
<input type="checkbox" data-ng-model="realm.requireSsl" class="onoffswitch-checkbox" name="requireSsl" id="requireSsl">
<label for="requireSsl" class="onoffswitch-label">
<span class="onoffswitch-inner">
<span class="onoffswitch-active">ON</span>
<span class="onoffswitch-inactive">OFF</span>
</span>
<span class="onoffswitch-switch"></span>
</label>
</div>
<input ng-model="realm.requireSsl" name="requireSsl" id="requireSsl" onoffswitch />
</div>
<div class="form-group">
<label for="cookieLoginAllowed" class="control-label">Cookie login allowed</label>
<div class="onoffswitch">
<input type="checkbox" data-ng-model="realm.cookieLoginAllowed" class="onoffswitch-checkbox" name="cookieLoginAllowed" id="cookieLoginAllowed">
<label for="cookieLoginAllowed" class="onoffswitch-label">
<span class="onoffswitch-inner">
<span class="onoffswitch-active">ON</span>
<span class="onoffswitch-inactive">OFF</span>
</span>
<span class="onoffswitch-switch"></span>
</label>
</div>
<input ng-model="realm.cookieLoginAllowed" name="cookieLoginAllowed" id="cookieLoginAllowed" onoffswitch />
</div>
</fieldset>
<div class="form-actions" data-ng-show="createRealm">
<button type="submit" data-ng-click="save()" class="primary" data-ng-show="changed">Save
</button>

View file

@ -1,5 +1,5 @@
<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') && '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] == 'smtp-settings') && 'active'"><a href="#/realms/{{realm.id}}">Settings</a></li>
<li data-ng-class="(path[2] == 'users' || path[1] == 'user') && 'active'"><a href="#/realms/{{realm.id}}/users">Users</a>
</li>
<li data-ng-class="(path[2] == 'applications' || path[1] == 'application') && 'active'"><a href="#/realms/{{realm.id}}/applications">Applications</a></li>

View file

@ -0,0 +1,95 @@
<div id="wrapper" class="container">
<div class="row">
<div class="bs-sidebar col-md-3 clearfix" data-ng-include data-src="'partials/realm-menu.html'"></div>
<div id="content-area" class="col-md-9" role="main">
<div class="top-nav" data-ng-hide="createRealm">
<ul class="rcue-tabs">
<li><a href="#/realms/{{realm.id}}">General</a></li>
<li data-ng-show="realm.social"><a href="#/realms/{{realm.id}}/social-settings">Social</a></li>
<li class="active"><a href="#/realms/{{realm.id}}/registration-settings">Registration</a></li>
<li><a href="#/realms/{{realm.id}}/roles">Roles</a></li>
<li><a href="#/realms/{{realm.id}}/required-credentials">Credentials</a></li>
<li><a href="#/realms/{{realm.id}}/token-settings">Token</a></li>
<li><a href="#/realms/{{realm.id}}/smtp-settings">SMTP</a></li>
</ul>
</div>
<div id="content">
<ol class="breadcrumb">
<li><a href="#/realms/{{realm.id}}">{{realm.realm}}</a></li>
<li><a href="#/realms/{{realm.id}}">Settings</a></li>
<li class="active">Registration</li>
</ol>
<h2><span>{{realm.realm}}</span> Registration Settings</h2>
<form name="realmForm" novalidate>
<fieldset>
<legend uncollapsed><span class="text">Realm Default Roles</span> </legend>
<div class="form-group">
<div class="controls changing-selectors">
<div class="select-title">
<label for="available">Available Roles</label>
<select id="available" class="form-control" multiple size="5"
ng-multiple="true"
ng-model="selectedRealmRoles"
ng-options="r for r in availableRealmRoles">
</select>
</div>
<div class="middle-buttons">
<button type="submit" ng-click="addRealmDefaultRole()" tooltip="Move right" tooltip-placement="right"><span class="icon-arrow-right">Move right</span></button>
<button type="submit" ng-click="rmRealmDefaultRole()" tooltip="Move left" tooltip-placement="right"><span class="icon-arrow-left">Move left</span></button>
</div>
<div class="select-title">
<label for="assigned">Realm Default Roles</label>
<select id="assigned" class="form-control" multiple size=5
ng-multiple="true"
ng-model="selectedRealmDefRoles"
ng-options="r for r in realm.defaultRoles">
</select>
</div>
</div>
</div>
</fieldset>
<fieldset ng-show="applications.length > 0">
<legend uncollapsed><span class="text">Application Default Roles</span> </legend>
<div class="form-group input-select">
<label for="applications">Application</label>
<div class="input-group">
<div class="select-rcue">
<select id="applications" name="applications" ng-change="changeApplication()" ng-model="application" ng-options="a.name for a in applications">
<option value="" selected> Select an Application...</option>
</select>
</div>
</div>
</div>
<div class="form-group" ng-show="application">
<div class="controls changing-selectors application">
<div class="select-title">
<label for="available-app">Available Roles</label>
<select id="available-app" class="form-control" multiple size="5"
ng-multiple="true"
ng-model="selectedAppRoles"
ng-options="r for r in availableAppRoles">
</select>
</div>
<div class="middle-buttons">
<button type="submit" ng-click="addAppDefaultRole()" tooltip="Move right" tooltip-placement="right"><span class="icon-arrow-right">Move right</span></button>
<button type="submit" ng-click="rmAppDefaultRole()" tooltip="Move left" tooltip-placement="right"><span class="icon-arrow-left">Move left</span></button>
</div>
<div class="select-title">
<label for="assigned-app">Assigned Default Roles</label>
<select id="assigned-app" class="form-control" multiple size=5
ng-multiple="true"
ng-model="selectedAppDefRoles"
ng-options="r for r in application.defaultRoles">
</select>
</div>
</div>
</div>
</fieldset>
</form>
</div>
</div>
<div id="container-right-bg"></div>
</div>
</div>

View file

@ -6,6 +6,7 @@
<ul class="rcue-tabs">
<li><a href="#/realms/{{realm.id}}">General</a></li>
<li data-ng-show="realm.social"><a href="#/realms/{{realm.id}}/social-settings">Social</a></li>
<li data-ng-show="realm.registrationAllowed"><a href="#/realms/{{realm.id}}/registration-settings">Registration</a></li>
<li><a href="#/realms/{{realm.id}}/roles">Roles</a></li>
<li><a href="#/realms/{{realm.id}}/required-credentials">Credentials</a></li>
<li><a href="#/realms/{{realm.id}}/token-settings">Token</a></li>
@ -42,45 +43,18 @@
</div>
<div class="form-group clearfix">
<label for="smtpSSL" class="control-label">Enable SSL</label>
<div class="onoffswitch">
<input type="checkbox" data-ng-model="realm.smtpServer.ssl" class="onoffswitch-checkbox" name="smtpSSL" id="smtpSSL">
<label for="smtpSSL" class="onoffswitch-label">
<span class="onoffswitch-inner">
<span class="onoffswitch-active">ON</span>
<span class="onoffswitch-inactive">OFF</span>
</span>
<span class="onoffswitch-switch"></span>
</label>
</div>
<input ng-model="realm.smtpServer.ssl" name="smtpSSL" id="smtpSSL" onoffswitch />
</div>
<div class="form-group clearfix">
<label for="smtpStartTLS" class="control-label">Enable StartTLS</label>
<div class="onoffswitch">
<input type="checkbox" data-ng-model="realm.smtpServer.starttls" class="onoffswitch-checkbox" name="smtpStartTLS" id="smtpStartTLS">
<label for="smtpStartTLS" class="onoffswitch-label">
<span class="onoffswitch-inner">
<span class="onoffswitch-active">ON</span>
<span class="onoffswitch-inactive">OFF</span>
</span>
<span class="onoffswitch-switch"></span>
</label>
</div>
<input ng-model="realm.smtpServer.starttls" name="smtpStartTLS" id="smtpStartTLS" onoffswitch />
</div>
</fieldset>
<fieldset>
<legend collapsed><span class="text">Authentication</span></legend>
<div class="form-group clearfix">
<label for="smtpAuth" class="control-label">Enabled</label>
<div class="onoffswitch">
<input type="checkbox" data-ng-model="realm.smtpServer.auth" class="onoffswitch-checkbox" name="smtpAuth" id="smtpAuth">
<label for="smtpAuth" class="onoffswitch-label">
<span class="onoffswitch-inner">
<span class="onoffswitch-active">ON</span>
<span class="onoffswitch-inactive">OFF</span>
</span>
<span class="onoffswitch-switch"></span>
</label>
</div>
<input ng-model="realm.smtpServer.auth" name="smtpAuth" id="smtpAuth" onoffswitch />
</div>
<div class="form-group clearfix">
<label for="smtpUsername" class="control-label">Username <span class="required" ng-show="realm.smtpServer.auth">*</span></label>

View file

@ -6,6 +6,7 @@
<ul class="rcue-tabs">
<li><a href="#/realms/{{realm.id}}">General</a></li>
<li class="active" data-ng-show="realm.social"><a href="#/realms/{{realm.id}}/social-settings">Social</a></li>
<li data-ng-show="realm.registrationAllowed"><a href="#/realms/{{realm.id}}/registration-settings">Registration</a></li>
<li><a href="#/realms/{{realm.id}}/roles">Roles</a></li>
<li><a href="#/realms/{{realm.id}}/required-credentials">Credentials</a></li>
<li><a href="#/realms/{{realm.id}}/token-settings">Token</a></li>
@ -30,7 +31,8 @@
<div class="actions">
<div class="select-rcue">
<select ng-model="newProviderId"
ng-options="p for p in unsetProviders"></select>
ng-options="p as allProviders[p] for p in unsetProviders"
placeholder="Please select"></select>
</div>
<div>
<button ng-click="addProvider()" ng-disabled="">Add Provider</button>
@ -49,16 +51,16 @@
<tr ng-repeat="pId in configuredProviders">
<td>
<div class="clearfix">
<input class="input-small disabled" type="text" placeholder="Key" value="{{pId}}" readonly>
<input class="input-small disabled" type="text" value="{{allProviders[pId]}}" readonly>
</div>
</td>
<td>
<input class="input-small" type="text" placeholder="Key" ng-model="realm.socialProviders[pId+'.key']"
ng-class="{'dirty': saveClicked}" required>
ng-class="{'dirty': postSaveProviders.indexOf(pId) > -1}" required>
</td>
<td>
<input class="input-small" type="text" placeholder="Secret" ng-model="realm.socialProviders[pId+'.secret']"
ng-class="{'dirty': saveClicked}" required>
ng-class="{'dirty': postSaveProviders.indexOf(pId) > -1}" required>
</td>
<td>
<div class="action-div"><i class="icon-question" ng-click="openHelp(pId)"></i></div>

View file

@ -6,6 +6,7 @@
<ul class="rcue-tabs">
<li><a href="#/realms/{{realm.id}}">General</a></li>
<li data-ng-show="realm.social"><a href="#/realms/{{realm.id}}/social-settings">Social</a></li>
<li data-ng-show="realm.registrationAllowed"><a href="#/realms/{{realm.id}}/registration-settings">Registration</a></li>
<li><a href="#/realms/{{realm.id}}/roles">Roles</a></li>
<li><a href="#/realms/{{realm.id}}/required-credentials">Credentials</a></li>
<li class="active"><a href="#/realms/{{realm.id}}/token-settings">Token</a></li>

View file

@ -6,6 +6,7 @@
<ul class="rcue-tabs">
<li><a href="#/realms/{{realm.id}}">General</a></li>
<li data-ng-show="realm.social"><a href="#/realms/{{realm.id}}/social-settings">Social</a></li>
<li data-ng-show="realm.registrationAllowed"><a href="#/realms/{{realm.id}}/registration-settings">Registration</a></li>
<li class="active"><a href="#/realms/{{realm.id}}/roles">Roles</a></li>
<li><a href="#/realms/{{realm.id}}/required-credentials">Credentials</a></li>
<li><a href="#/realms/{{realm.id}}/token-settings">Token</a></li>

View file

@ -14,7 +14,6 @@ public class ApplicationRepresentation {
protected String adminUrl;
protected String baseUrl;
protected boolean surrogateAuthRequired;
protected boolean useRealmMappings;
protected boolean enabled;
protected List<CredentialRepresentation> credentials;
protected List<RoleRepresentation> roles;
@ -142,14 +141,6 @@ public class ApplicationRepresentation {
return this;
}
public boolean isUseRealmMappings() {
return useRealmMappings;
}
public void setUseRealmMappings(boolean useRealmMappings) {
this.useRealmMappings = useRealmMappings;
}
public List<String> getRedirectUris() {
return redirectUris;
}

View file

@ -33,6 +33,7 @@ public class RealmRepresentation {
protected Set<String> requiredApplicationCredentials;
protected Set<String> requiredOAuthClientCredentials;
protected List<UserRepresentation> users;
protected List<UserRepresentation> clients;
protected List<UserRoleMappingRepresentation> roleMappings;
protected List<ScopeMappingRepresentation> scopeMappings;
protected List<SocialMappingRepresentation> socialMappings;
@ -68,6 +69,10 @@ public class RealmRepresentation {
return users;
}
public List<UserRepresentation> getClients() {
return clients;
}
public List<ApplicationRepresentation> getApplications() {
return applications;
}
@ -84,6 +89,10 @@ public class RealmRepresentation {
this.users = users;
}
public void setClients(List<UserRepresentation> clients) {
this.clients = clients;
}
public UserRepresentation user(String username) {
UserRepresentation user = new UserRepresentation();
user.setUsername(username);

2
dist/assembly.xml vendored
View file

@ -16,7 +16,7 @@
<excludes>
<exclude>**/*.sh</exclude>
<exclude>domain/tmp/auth</exclude>
<exclude>domain/tmp/auth</exclude>
<exclude>standalone/tmp/auth</exclude>
<exclude>**/*-users.properties</exclude>
</excludes>
</fileSet>

14
dist/build.xml vendored
View file

@ -1,19 +1,20 @@
<project name="keycloak-dist" basedir=".">
<target name="jboss">
<unzip src="${org.jboss.as:jboss-as-dist:zip}" dest="${project.build.directory}"/>
<target name="wildfly">
<unzip src="${org.wildfly:wildfly-dist:zip}" dest="${project.build.directory}"/>
<chmod perm="755">
<fileset dir="${project.build.directory}/jboss-as-${jboss.version}/bin">
<fileset dir="${project.build.directory}/wildfly-${wildfly.version}/bin">
<include name="**/*.sh"/>
</fileset>
</chmod>
<move todir="${build.target.dir}" overwrite="true">
<fileset dir="${project.build.directory}/jboss-as-${jboss.version}">
<fileset dir="${project.build.directory}/wildfly-${wildfly.version}">
<include name="**/*"/>
</fileset>
</move>
<delete dir="${project.build.directory}/jboss-as-${jboss.version}"/>
<delete dir="${project.build.directory}/wildfly-${wildfly.version}"/>
</target>
<!--
<target name="resteasy-modules">
<get src="http://sourceforge.net/projects/resteasy/files/Resteasy%20JAX-RS/${resteasy.version}/resteasy-jaxrs-${resteasy.version}-all.zip"
dest="${project.build.directory}" skipexisting="true"/>
@ -27,11 +28,12 @@
<unzip src="${project.build.directory}/resteasy-jboss-modules-${resteasy.version}.zip"
dest="${build.target.dir}/modules"/>
</target>
-->
<target name="keycloak-server">
<copy file="${org.keycloak:keycloak-server:war}"
tofile="${build.target.dir}/standalone/deployments/auth-server.war" overwrite="true"/>
</target>
<target name="all" depends="jboss, resteasy-modules, keycloak-server"/>
<target name="all" depends="wildfly, keycloak-server"/>
</project>

34
dist/pom.xml vendored
View file

@ -35,9 +35,9 @@
<type>war</type>
</dependency>
<dependency>
<groupId>org.jboss.as</groupId>
<artifactId>jboss-as-dist</artifactId>
<version>${jboss.version}</version>
<groupId>org.wildfly</groupId>
<artifactId>wildfly-dist</artifactId>
<version>${wildfly.version}</version>
<type>zip</type>
</dependency>
</dependencies>
@ -66,6 +66,34 @@
</executions>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>xml-maven-plugin</artifactId>
<version>1.0</version>
<executions>
<execution>
<id>generate-resources</id>
<phase>package</phase>
<goals>
<goal>transform</goal>
</goals>
<configuration>
<transformationSets>
<transformationSet>
<dir>${build.target.dir}/standalone/configuration</dir>
<stylesheet>src/main/xslt/standalone.xsl</stylesheet>
<includes>
<include>standalone*.xml</include>
</includes>
<outputDir>${build.target.dir}/standalone/configuration</outputDir>
</transformationSet>
</transformationSets>
<targetDirectory>${project.build.directory}</targetDirectory>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>

33
dist/src/main/xslt/standalone.xsl vendored Normal file
View file

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xalan="http://xml.apache.org/xalan"
xmlns:j="urn:jboss:domain:1.3"
version="2.0"
exclude-result-prefixes="xalan j">
<xsl:param name="config"/>
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes" xalan:indent-amount="4" standalone="no"/>
<xsl:strip-space elements="*"/>
<xsl:template match="node()[name(.)='datasources']">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
<datasource jndi-name="java:jboss/datasources/KeycloakDS" pool-name="KeycloakDS" enabled="true" use-java-context="true">
<connection-url>jdbc:h2:${jboss.server.data.dir}/keycloak;DB_CLOSE_DELAY=-1</connection-url>
<driver>h2</driver>
<security>
<user-name>sa</user-name>
<password>sa</password>
</security>
</datasource>
</xsl:copy>
</xsl:template>
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>

View file

@ -112,6 +112,11 @@
<version>4.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jboss.spec.javax.servlet</groupId>
<artifactId>jboss-servlet-api_3.0_spec</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
<build>

View file

@ -26,7 +26,9 @@
{ "type" : "password",
"value" : "password" }
]
},
}
],
"clients" : [
{
"username" : "third-party",
"enabled": true,
@ -50,10 +52,6 @@
{
"username": "bburke@redhat.com",
"roles": ["user"]
},
{
"username": "third-party",
"roles": ["KEYCLOAK_IDENTITY_REQUESTER"]
}
],
"scopeMappings": [
@ -67,7 +65,6 @@
"name": "customer-portal",
"enabled": true,
"adminUrl": "http://localhost:8080/customer-portal/j_admin_request",
"useRealmMappings": true,
"credentials": [
{
"type": "password",
@ -79,7 +76,6 @@
"name": "product-portal",
"enabled": true,
"adminUrl": "http://localhost:8080/product-portal/j_admin_request",
"useRealmMappings": true,
"credentials": [
{
"type": "password",

View file

@ -21,10 +21,6 @@
<async-supported>true</async-supported>
</servlet>
<listener>
<listener-class>org.keycloak.services.listeners.MongoRunnerListener</listener-class>
</listener>
<filter>
<filter-name>Keycloak Session Management</filter-name>
<filter-class>org.keycloak.services.filters.KeycloakSessionServletFilter</filter-class>

View file

@ -95,6 +95,11 @@
<version>4.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jboss.spec.javax.servlet</groupId>
<artifactId>jboss-servlet-api_3.0_spec</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
<build>

View file

@ -26,7 +26,9 @@
{ "type" : "password",
"value" : "password" }
]
},
}
],
"clients" : [
{
"username" : "third-party",
"enabled": true,
@ -50,10 +52,6 @@
{
"username": "bburke@redhat.com",
"roles": ["user"]
},
{
"username": "third-party",
"roles": ["KEYCLOAK_IDENTITY_REQUESTER"]
}
],
"scopeMappings": [
@ -67,7 +65,6 @@
"name": "customer-portal",
"enabled": true,
"adminUrl": "http://localhost:8080/customer-portal/j_admin_request",
"useRealmMappings": true,
"webOrigins" : [ "http://localhost1:8080"],
"credentials": [
{
@ -80,7 +77,6 @@
"name": "product-portal",
"enabled": true,
"adminUrl": "http://localhost:8080/product-portal/j_admin_request",
"useRealmMappings": true,
"credentials": [
{
"type": "password",

View file

@ -48,7 +48,6 @@
"name": "test-app",
"enabled": true,
"adminUrl": "http://localhost:8081/app/logout",
"useRealmMappings": true,
"webOrigins": [ "http://localhost", "http://localhost:8000", "http://localhost:8080" ],
"credentials": [
{

View file

@ -38,18 +38,6 @@
<artifactId>resteasy-jaxrs</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.jboss.spec.javax.servlet</groupId>
<artifactId>jboss-servlet-api_3.0_spec</artifactId>
</dependency>
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>core</artifactId>
</dependency>
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>javase</artifactId>
</dependency>
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>

View file

@ -104,7 +104,7 @@ public class FormServiceImpl implements FormService {
Configuration cfg = new Configuration();
try {
cfg.setClassForTemplateLoading(FormServiceImpl.class,"/META-INF/resources");
cfg.setClassForTemplateLoading(FormServiceImpl.class,"/META-INF/resources/forms/theme/default");
Template template = cfg.getTemplate(temp);
template.process(input, out);

View file

@ -1 +0,0 @@
<#include "./theme/" + template.theme + "/access.ftl">

View file

@ -1 +0,0 @@
<#include "./theme/" + template.theme + "/account.ftl">

View file

@ -1 +0,0 @@
<#include "./theme/" + template.theme + "/error.ftl">

View file

@ -1 +0,0 @@
<#include "./theme/" + template.theme + "/login-config-totp.ftl">

View file

@ -1 +0,0 @@
<#include "./theme/" + template.theme + "/login-oauth-grant.ftl">

View file

@ -1 +0,0 @@
<#include "./theme/" + template.theme + "/login-reset-password.ftl">

View file

@ -1 +0,0 @@
<#include "./theme/" + template.theme + "/login-totp.ftl">

View file

@ -1 +0,0 @@
<#include "./theme/" + template.theme + "/login-update-password.ftl">

View file

@ -1 +0,0 @@
<#include "./theme/" + template.theme + "/login-update-profile.ftl">

View file

@ -1 +0,0 @@
<#include "./theme/" + template.theme + "/login-verify-email.ftl">

View file

@ -1 +0,0 @@
<#include "./theme/" + template.theme + "/login.ftl">

View file

@ -1 +0,0 @@
<#include "./theme/" + template.theme + "/password.ftl">

View file

@ -1 +0,0 @@
<#include "./theme/" + template.theme + "/register.ftl">

View file

@ -1 +0,0 @@
<#include "./theme/" + template.theme + "/social.ftl">

View file

@ -71,7 +71,7 @@ body {
width: auto;
position: relative;
}
.rcue-login-register .background-area .separator .section,
.rcue-login-register .background-area .section,
.rcue-login-register .background-area .social .section {
padding-top: 1.5em;
padding-bottom: 1.5em;
@ -303,10 +303,24 @@ a.zocial:before {
.rcue-login-register .background-area .content-area ul li {
border-top: 1px solid #34393C;
padding: 2em;
position: relative;
}
.rcue-login-register .background-area .content-area ul li span:first-child {
.rcue-login-register .background-area .content-area ul li span {
font-size: 1.3em;
line-height: 1.3em;
}
.rcue-login-register .background-area .content-area ul li span:first-child {
padding-right: 11.5384615384615em;
}
.rcue-login-register .background-area .content-area ul li span.parent {
position: absolute;
left: 26em;
top: 1.53846153846154em;
width: 12.3076923076923em;
}
.rcue-login-register .background-area .content-area ul li span.icon-info {
float: right;
margin-top: 0.5em;

View file

@ -19,17 +19,13 @@
</li>
</#list>
<#list oauth.resourceRolesRequested?keys as resourceRole>
<li>
<strong>${resourceRole}</strong>
<ul>
<#list oauth.resourceRolesRequested[resourceRole] as role>
<#list oauth.resourceRolesRequested?keys as resource>
<#list oauth.resourceRolesRequested[resource] as role>
<li>
<span><#if role.description??>${role.description}<#else>${role.name}</#if></span>
<span class="parent">in <strong>${resource}</strong></span>
</li>
</#list>
</ul>
</li>
</#list>
</ul>

View file

@ -1,5 +1,5 @@
<#import "template-login-action.ftl" as layout>
<@layout.registrationLayout bodyClass="reset"; section>
<@layout.registrationLayout bodyClass="reset" isSeparator=true forceSeparator=true; section>
<#if section = "title">
${rb.getString('emailForgotHeader')}
@ -23,6 +23,6 @@
</form>
</div>
<#elseif section = "info" >
<p><a href="#">&laquo; Back to Login</a></p>
<p><a href="${url.loginUrl}">&laquo; Back to Login</a></p>
</#if>
</@layout.registrationLayout>

View file

@ -1,5 +1,5 @@
<#import "template-login-action.ftl" as layout>
<@layout.registrationLayout bodyClass="reset"; section>
<@layout.registrationLayout bodyClass="reset" isSeparator=false forceSeparator=true; section>
<#if section = "title">
${rb.getString('emailUpdateHeader')}

View file

@ -1,5 +1,5 @@
<#import "template-login-action.ftl" as layout>
<@layout.registrationLayout bodyClass=""; section>
<@layout.registrationLayout bodyClass="" isSeparator=false forceSeparator=true; section>
<#if section = "title">
Update Account Information

View file

@ -1,5 +1,5 @@
<#import "template-login-action.ftl" as layout>
<@layout.registrationLayout bodyClass="email"; section>
<@layout.registrationLayout bodyClass="email" isSeparator=false forceSeparator=true; section>
<#if section = "title">
Email verification

View file

@ -1,4 +1,4 @@
<#macro registrationLayout bodyClass isErrorPage=false>
<#macro registrationLayout bodyClass isErrorPage=false isSeparator=false forceSeparator=false>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
@ -7,6 +7,7 @@
<title>
<#nested "title">
</title>
<link rel="icon" href="${template.formsPath}/theme/${template.theme}/img/favicon.ico">
<link href="${template.themeConfig.styles}" rel="stylesheet" />
<style type="text/css">
body.rcue-login-register {
@ -25,7 +26,7 @@
</div>
<#if (template.themeConfig.logo)?has_content>
<h1>
<a href="#" title="Go to the login page"><img src="${template.themeConfig.logo}" alt="Red Hat Logo" /></a>
<a href="${url.loginUrl}" title="Go to the login page"><img src="${template.themeConfig.logo}" alt="Red Hat Logo" /></a>
</h1>
</#if>
@ -35,7 +36,12 @@
</h2>
<div class="background-area">
<div class="form-area clearfix">
<#if !forceSeparator && realm?has_content>
<#assign drawSeparator = realm.registrationAllowed>
<#else>
<#assign drawSeparator = isSeparator>
</#if>
<div class="form-area clearfix ${(drawSeparator)?string('separator','')}">
<div class="section app-form">
<#if !isErrorPage && message?has_content>
<#if message.error>

View file

@ -1,4 +1,4 @@
<#macro registrationLayout bodyClass>
<#macro registrationLayout bodyClass isSeparator=false forceSeparator=false>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
@ -7,6 +7,7 @@
<title>
<#nested "title">
</title>
<link rel="icon" href="${template.formsPath}/theme/${template.theme}/img/favicon.ico">
<link href="${template.themeConfig.styles}" rel="stylesheet" />
<style type="text/css">
body.rcue-login-register {
@ -18,7 +19,7 @@
<body class="rcue-login-register ${bodyClass}">
<#if (template.themeConfig.logo)?has_content>
<h1>
<a href="#" title="Go to the login page"><img src="${template.themeConfig.logo}" alt="Red Hat Logo" /></a>
<a href="${url.loginUrl}" title="Go to the login page"><img src="${template.themeConfig.logo}" alt="Red Hat Logo" /></a>
</h1>
</#if>
@ -28,7 +29,12 @@
</h2>
<div class="background-area">
<div class="form-area ${(realm.social)?string('social','')} clearfix">
<#if !forceSeparator && realm?has_content>
<#assign drawSeparator = realm.registrationAllowed>
<#else>
<#assign drawSeparator = isSeparator>
</#if>
<div class="form-area ${(realm.social && bodyClass != "register")?string('social','')} ${(drawSeparator)?string('separator','')} clearfix">
<div class="section app-form">
<h3>Application login area</h3>
<#if message?has_content && message.error>

View file

@ -4,8 +4,7 @@
<head>
<meta charset="utf-8">
<title>Edit Account - <#nested "title"></title>
<!-- TODO replace with actual logo once
<link rel="icon" href="img/favicon.ico">
<link rel="icon" href="${template.formsPath}/theme/${template.theme}/img/favicon.ico">
<!-- Frameworks -->
<link rel="stylesheet" href="${template.formsPath}/theme/${template.theme}/css/reset.css">

View file

@ -1 +0,0 @@
<#include "./theme/" + template.theme + "/totp.ftl">

View file

@ -22,15 +22,15 @@ passwordNewConfirm=New Password confirmation
authenticatorCode=One-time-password
clientCertificate=Client Certificate
invalidUser=Invalid username or password
invalidPassword=Invalid username or password
invalidUser=Invalid username or password.
invalidPassword=Invalid username or password.
accountDisabled=Account is disabled, contact admin
missingFirstName=Please specify first name
missingLastName=Please specify last name
missingEmail=Please specify email
missingUsername=Please specify username
missingPassword=Please specify password
missingPassword=Please specify password.
notMatchPassword=Passwords don't match
missingTotp=Please specify authenticator code
@ -49,7 +49,7 @@ actionTotpWarning=You need to set up the Google Authenticator to activate your a
actionProfileWarning=You need to update your user profile to activate your account.
actionPasswordWarning=You need to change your password to activate your account.
actionEmailWarning=You need to verify your email address to activate your account.
actionFollow=Please follow the steps below.
actionFollow=Please fill in the fields below.
successHeader=Success!
errorHeader=Error!

View file

@ -5,16 +5,14 @@ package org.keycloak.models;
* @version $Revision: 1 $
*/
public interface Constants {
String INTERNAL_ROLE = "KEYCLOAK_";
String ADMIN_REALM = "Keycloak Administration";
String ADMIN_CONSOLE_APPLICATION = "Admin Console";
String ADMIN_CONSOLE_ADMIN_ROLE = "admin";
String APPLICATION_ROLE = "KEYCLOAK_APPLICATION";
String IDENTITY_REQUESTER_ROLE = "KEYCLOAK_IDENTITY_REQUESTER";
String WILDCARD_ROLE = "*";
String APPLICATION_ROLE = INTERNAL_ROLE + "_APPLICATION";
String IDENTITY_REQUESTER_ROLE = INTERNAL_ROLE + "_IDENTITY_REQUESTER";
String ACCOUNT_APPLICATION = "Account";
String ACCOUNT_PROFILE_ROLE = "view-profile";
String ACCOUNT_MANAGE_ROLE = "manage-account";
String ACCOUNT_MANAGEMENT_APPLICATION = "Account Management";
}

View file

@ -5,5 +5,7 @@ package org.keycloak.models;
* @version $Revision: 1 $
*/
public interface ModelProvider {
String getId();
KeycloakSessionFactory createFactory();
}

View file

@ -84,6 +84,8 @@ public interface RealmModel extends RoleContainerModel, RoleMapperModel, ScopeMa
UserModel addUser(String username);
boolean deleteUser(String name);
List<String> getDefaultRoles();
void addDefaultRole(String name);

View file

@ -34,6 +34,30 @@
<artifactId>hibernate-jpa-2.0-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>${hibernate.entitymanager.version}</version>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-jaxrs</artifactId>
<scope>provided</scope>
<exclusions>
<exclusion>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
</exclusion>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</exclusion>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>

View file

@ -11,6 +11,12 @@ import javax.persistence.Persistence;
* @version $Revision: 1 $
*/
public class JpaModelProvider implements ModelProvider {
@Override
public String getId() {
return "jpa";
}
@Override
public KeycloakSessionFactory createFactory() {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpa-keycloak-identity-store");

View file

@ -11,6 +11,7 @@ import org.keycloak.models.SocialLinkModel;
import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.jpa.entities.ApplicationEntity;
import org.keycloak.models.jpa.entities.ApplicationUserRoleMappingEntity;
import org.keycloak.models.jpa.entities.CredentialEntity;
import org.keycloak.models.jpa.entities.OAuthClientEntity;
import org.keycloak.models.jpa.entities.RealmEntity;
@ -456,6 +457,25 @@ public class RealmAdapter implements RealmModel {
return new UserAdapter(entity);
}
@Override
public boolean deleteUser(String name) {
TypedQuery<UserEntity> query = em.createNamedQuery("getRealmUserByLoginName", UserEntity.class);
query.setParameter("loginName", name);
query.setParameter("realm", realm);
List<UserEntity> results = query.getResultList();
if (results.size() == 0) return false;
UserEntity user = results.get(0);
for (Class r : UserEntity.RELATIONSHIPS) {
em.createQuery("delete from " + r.getSimpleName() + " where user = :user").setParameter("user", user).executeUpdate();
}
em.remove(user);
return true;
}
@Override
public List<String> getDefaultRoles() {
Collection<RoleEntity> entities = realm.getDefaultRoles();
@ -548,8 +568,6 @@ public class RealmAdapter implements RealmModel {
em.persist(applicationData);
em.flush();
ApplicationModel resource = new ApplicationAdapter(em, applicationData);
resource.addRole("*");
resource.addScopeMapping(new UserAdapter(user), "*");
em.flush();
return resource;
}
@ -643,13 +661,13 @@ public class RealmAdapter implements RealmModel {
for (Map.Entry<String, String> entry : attributes.entrySet()) {
String attribute = null;
if (entry.getKey().equals(UserModel.LOGIN_NAME)) {
attribute = "loginName";
attribute = "lower(loginName)";
} else if (entry.getKey().equalsIgnoreCase(UserModel.FIRST_NAME)) {
attribute = "firstName";
attribute = "lower(firstName)";
} else if (entry.getKey().equalsIgnoreCase(UserModel.LAST_NAME)) {
attribute = "lastName";
attribute = "lower(lastName)";
} else if (entry.getKey().equalsIgnoreCase(UserModel.EMAIL)) {
attribute = "email";
attribute = "lower(email)";
}
if (attribute == null) continue;
if (first) {
@ -658,9 +676,10 @@ public class RealmAdapter implements RealmModel {
} else {
builder.append(" and ");
}
builder.append(attribute).append("='").append(entry.getValue()).append("'");
builder.append(attribute).append(" like '%").append(entry.getValue().toLowerCase()).append("%'");
}
TypedQuery<UserEntity> query = em.createQuery(builder.toString(), UserEntity.class);
String q = builder.toString();
TypedQuery<UserEntity> query = em.createQuery(q, UserEntity.class);
List<UserEntity> results = query.getResultList();
List<UserModel> users = new ArrayList<UserModel>();
for (UserEntity entity : results) users.add(new UserAdapter(entity));

View file

@ -4,6 +4,7 @@ import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinTable;
import javax.persistence.OneToMany;
@ -18,7 +19,7 @@ import java.util.Collection;
@Entity
public class ApplicationEntity {
@Id
@GeneratedValue
@GeneratedValue(strategy = GenerationType.IDENTITY)
private String id;
private String name;

View file

@ -2,6 +2,7 @@ package org.keycloak.models.jpa.entities;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import javax.persistence.NamedQueries;
@ -17,7 +18,7 @@ import javax.persistence.NamedQuery;
@Entity
public class CredentialEntity {
@Id
@GeneratedValue
@GeneratedValue(strategy = GenerationType.IDENTITY)
protected String id;
protected String type;

View file

@ -3,6 +3,7 @@ package org.keycloak.models.jpa.entities;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import javax.persistence.NamedQueries;
@ -21,7 +22,7 @@ import javax.persistence.OneToOne;
@Entity
public class OAuthClientEntity {
@Id
@GeneratedValue
@GeneratedValue(strategy = GenerationType.IDENTITY)
private String id;
private String name;

View file

@ -2,6 +2,7 @@ package org.keycloak.models.jpa.entities;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
/**
@ -11,7 +12,7 @@ import javax.persistence.Id;
@Entity
public class RequiredCredentialEntity {
@Id
@GeneratedValue
@GeneratedValue(strategy = GenerationType.IDENTITY)
protected String id;
protected String type;

View file

@ -2,6 +2,7 @@ package org.keycloak.models.jpa.entities;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
/**
@ -11,7 +12,7 @@ import javax.persistence.Id;
@Entity
public class RoleEntity {
@Id
@GeneratedValue
@GeneratedValue(strategy = GenerationType.IDENTITY)
private String id;
private String name;

View file

@ -2,6 +2,7 @@ package org.keycloak.models.jpa.entities;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import javax.persistence.NamedQueries;
@ -19,7 +20,7 @@ import javax.persistence.NamedQuery;
@Entity
public class SocialLinkEntity {
@Id
@GeneratedValue
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
@ManyToOne

View file

@ -2,11 +2,13 @@ package org.keycloak.models.jpa.entities;
import org.keycloak.models.UserModel;
import javax.persistence.CascadeType;
import javax.persistence.CollectionTable;
import javax.persistence.Column;
import javax.persistence.ElementCollection;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import javax.persistence.MapKeyColumn;
@ -32,8 +34,11 @@ import java.util.Set;
})
@Entity
public class UserEntity {
public static final Class[] RELATIONSHIPS = new Class[] { ApplicationUserRoleMappingEntity.class, RealmUserRoleMappingEntity.class, SocialLinkEntity.class };
@Id
@GeneratedValue
@GeneratedValue(strategy = GenerationType.IDENTITY)
protected String id;
protected String loginName;
@ -65,7 +70,7 @@ public class UserEntity {
@CollectionTable
protected Set<String> redirectUris = new HashSet<String>();
@OneToMany
@OneToMany(cascade = CascadeType.REMOVE, orphanRemoval = true)
protected Collection<CredentialEntity> credentials = new ArrayList<CredentialEntity>();
public String getId() {

View file

@ -1,6 +1,7 @@
package org.keycloak.models.jpa.entities;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import javax.persistence.MappedSuperclass;
@ -12,7 +13,7 @@ import javax.persistence.MappedSuperclass;
@MappedSuperclass
public abstract class UserRoleMappingEntity {
@Id
@GeneratedValue
@GeneratedValue(strategy = GenerationType.IDENTITY)
protected long id;
@ManyToOne
protected UserEntity user;

View file

@ -0,0 +1 @@
org.keycloak.models.jpa.JpaModelProvider

View file

@ -0,0 +1,58 @@
package org.keycloak.models.mongo;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class PropertiesManager {
private static final String MONGO_HOST = "keycloak.mongodb.host";
private static final String MONGO_PORT = "keycloak.mongodb.port";
private static final String MONGO_DB_NAME = "keycloak.mongodb.databaseName";
private static final String MONGO_DROP_DB_ON_STARTUP = "keycloak.mongodb.dropDatabaseOnStartup";
private static final String BOOTSTRAP_EMBEDDED_MONGO_AT_CONTEXT_INIT = "keycloak.mongodb.bootstrapEmbeddedMongoAtContextInit";
// Port where embedded MongoDB will be started during keycloak bootstrap. Same port will be used by KeycloakApplication then
private static final int MONGO_DEFAULT_PORT_KEYCLOAK_WAR_EMBEDDED = 37017;
// Port where MongoDB instance is normally started on linux. This port should be used if we're not starting embedded instance (keycloak.mongodb.bootstrapEmbeddedMongoAtContextInit is false)
private static final int MONGO_DEFAULT_PORT_KEYCLOAK_WAR = 27017;
// Port where unit tests will start embedded MongoDB instance
public static final int MONGO_DEFAULT_PORT_UNIT_TESTS = 27777;
public static String getMongoHost() {
return System.getProperty(MONGO_HOST, "localhost");
}
public static void setMongoHost(String mongoHost) {
System.setProperty(MONGO_HOST, mongoHost);
}
public static int getMongoPort() {
return Integer.parseInt(System.getProperty(MONGO_PORT, String.valueOf(MONGO_DEFAULT_PORT_KEYCLOAK_WAR_EMBEDDED)));
}
public static void setMongoPort(int mongoPort) {
System.setProperty(MONGO_PORT, String.valueOf(mongoPort));
}
public static String getMongoDbName() {
return System.getProperty(MONGO_DB_NAME, "keycloak");
}
public static void setMongoDbName(String mongoMongoDbName) {
System.setProperty(MONGO_DB_NAME, mongoMongoDbName);
}
public static boolean dropDatabaseOnStartup() {
return Boolean.parseBoolean(System.getProperty(MONGO_DROP_DB_ON_STARTUP, "true"));
}
public static void setDropDatabaseOnStartup(boolean dropDatabaseOnStartup) {
System.setProperty(MONGO_DROP_DB_ON_STARTUP, String.valueOf(dropDatabaseOnStartup));
}
public static boolean bootstrapEmbeddedMongoAtContextInit() {
return isMongoSessionFactory() && Boolean.parseBoolean(System.getProperty(BOOTSTRAP_EMBEDDED_MONGO_AT_CONTEXT_INIT, "true"));
}
}

View file

@ -3,13 +3,31 @@ package org.keycloak.models.mongo.keycloak;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.ModelProvider;
import java.lang.Override;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class MongoModelProvider implements ModelProvider {
@Override
public String getId() {
return "mongo";
}
@Override
public KeycloakSessionFactory createFactory() {
return null; //To change body of implemented methods use File | Settings | File Templates.
String host = PropertiesManager.getMongoHost();
int port = PropertiesManager.getMongoPort();
String dbName = PropertiesManager.getMongoDbName();
boolean dropDatabaseOnStartup = PropertiesManager.dropDatabaseOnStartup();
// Create MongoDBSessionFactory via reflection now
try {
return new MongoDBSessionFactory(host, port, dbName, dropDatabaseOnStartup);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}

View file

@ -451,8 +451,6 @@ public class RealmAdapter implements RealmModel {
noSQL.saveObject(appData);
ApplicationModel resource = new ApplicationAdapter(appData, noSQL);
resource.addRole("*");
resource.addScopeMapping(resourceUser, "*");
return resource;
}

View file

@ -0,0 +1 @@
org.keycloak.models.mongo.keycloak.MongoModelProvider

View file

@ -37,27 +37,22 @@
<dependency>
<groupId>org.picketlink</groupId>
<artifactId>picketlink-idm-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.picketlink</groupId>
<artifactId>picketlink-common</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.picketlink</groupId>
<artifactId>picketlink-idm-impl</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.picketlink</groupId>
<artifactId>picketlink-idm-simple-schema</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.picketlink</groupId>
<artifactId>picketlink-config</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.hibernate.javax.persistence</groupId>

View file

@ -37,6 +37,11 @@ public class PicketlinkModelProvider implements ModelProvider {
return new PicketlinkKeycloakSessionFactory(emf, buildPartitionManager());
}
@Override
public String getId() {
return "picketlink";
}
public static PartitionManager buildPartitionManager() {
IdentityConfigurationBuilder builder = new IdentityConfigurationBuilder();

View file

@ -518,6 +518,16 @@ public class RealmAdapter implements RealmModel {
return new UserAdapter(user, getIdm());
}
@Override
public boolean deleteUser(String name) {
User user = findPicketlinkUser(name);
if (user == null) {
return false;
}
getIdm().remove(user);
return true;
}
@Override
public RoleAdapter getRole(String name) {
Role role = SampleModel.getRole(getIdm(), name);
@ -615,8 +625,6 @@ public class RealmAdapter implements RealmModel {
resourceRelationship.setApplication(applicationData.getName());
getRelationshipManager().add(resourceRelationship);
ApplicationModel resource = new ApplicationAdapter(applicationData, this, partitionManager);
resource.addRole("*");
resource.addScopeMapping(new UserAdapter(resourceUser, idm), "*");
return resource;
}

View file

@ -0,0 +1 @@
org.keycloak.models.picketlink.PicketlinkModelProvider

View file

@ -20,7 +20,7 @@
<dom4j.version>1.6.1</dom4j.version>
<mysql.version>5.1.25</mysql.version>
<slf4j.version>1.6.1</slf4j.version>
<jboss.version>7.1.1.Final</jboss.version>
<wildfly.version>8.0.0.Beta1</wildfly.version>
</properties>
<url>http://keycloak.org</url>

View file

@ -45,11 +45,6 @@
<artifactId>keycloak-model-api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-model-picketlink</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-model-jpa</artifactId>
@ -64,6 +59,24 @@
<groupId>org.keycloak</groupId>
<artifactId>keycloak-social-google</artifactId>
<version>${project.version}</version>
<exclusions>
<exclusion>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</exclusion>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
<exclusion>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
</exclusion>
<exclusion>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-core-asl</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
@ -80,56 +93,15 @@
<artifactId>keycloak-forms</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.picketlink</groupId>
<artifactId>picketlink-idm-api</artifactId>
</dependency>
<dependency>
<groupId>org.picketlink</groupId>
<artifactId>picketlink-idm-impl</artifactId>
</dependency>
<dependency>
<groupId>org.picketlink</groupId>
<artifactId>picketlink-idm-simple-schema</artifactId>
</dependency>
<dependency>
<groupId>org.picketlink</groupId>
<artifactId>picketlink-config</artifactId>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-jaxrs</artifactId>
<scope>provided</scope>
<exclusions>
<exclusion>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
</exclusion>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</exclusion>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>jaxrs-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.3.161</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.1</version>
<scope>test</scope>
<groupId>org.jboss.spec.javax.servlet</groupId>
<artifactId>jboss-servlet-api_3.0_spec</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>

View file

@ -0,0 +1,8 @@
<jboss-deployment-structure>
<deployment>
<dependencies>
<module name="org.apache.httpcomponents"/>
<module name="org.codehaus.jackson.jackson-core-asl"/>
</dependencies>
</deployment>
</jboss-deployment-structure>

View file

@ -2,32 +2,26 @@
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"
version="1.0">
<persistence-unit name="keycloak-identity-store" transaction-type="RESOURCE_LOCAL">
<jta-data-source>java:jboss/datasources/ExampleDS</jta-data-source>
<class>org.picketlink.idm.jpa.model.sample.simple.AttributedTypeEntity</class>
<class>org.picketlink.idm.jpa.model.sample.simple.AccountTypeEntity</class>
<class>org.picketlink.idm.jpa.model.sample.simple.RoleTypeEntity</class>
<class>org.picketlink.idm.jpa.model.sample.simple.GroupTypeEntity</class>
<class>org.picketlink.idm.jpa.model.sample.simple.IdentityTypeEntity</class>
<class>org.picketlink.idm.jpa.model.sample.simple.RelationshipTypeEntity</class>
<class>org.picketlink.idm.jpa.model.sample.simple.RelationshipIdentityTypeEntity</class>
<class>org.picketlink.idm.jpa.model.sample.simple.PartitionTypeEntity</class>
<class>org.picketlink.idm.jpa.model.sample.simple.PasswordCredentialTypeEntity</class>
<class>org.picketlink.idm.jpa.model.sample.simple.DigestCredentialTypeEntity</class>
<class>org.picketlink.idm.jpa.model.sample.simple.X509CredentialTypeEntity</class>
<class>org.picketlink.idm.jpa.model.sample.simple.OTPCredentialTypeEntity</class>
<class>org.picketlink.idm.jpa.model.sample.simple.AttributeTypeEntity</class>
<class>org.keycloak.models.picketlink.mappings.RealmEntity</class>
<class>org.keycloak.models.picketlink.mappings.ApplicationEntity</class>
<persistence-unit name="jpa-keycloak-identity-store" transaction-type="RESOURCE_LOCAL">
<jta-data-source>java:jboss/datasources/KeycloakDS</jta-data-source>
<class>org.keycloak.models.jpa.entities.ApplicationEntity</class>
<class>org.keycloak.models.jpa.entities.ApplicationScopeMappingEntity</class>
<class>org.keycloak.models.jpa.entities.ApplicationUserRoleMappingEntity</class>
<class>org.keycloak.models.jpa.entities.CredentialEntity</class>
<class>org.keycloak.models.jpa.entities.OAuthClientEntity</class>
<class>org.keycloak.models.jpa.entities.RealmEntity</class>
<class>org.keycloak.models.jpa.entities.RealmScopeMappingEntity</class>
<class>org.keycloak.models.jpa.entities.RealmUserRoleMappingEntity</class>
<class>org.keycloak.models.jpa.entities.RequiredCredentialEntity</class>
<class>org.keycloak.models.jpa.entities.RoleEntity</class>
<class>org.keycloak.models.jpa.entities.SocialLinkEntity</class>
<class>org.keycloak.models.jpa.entities.UserEntity</class>
<class>org.keycloak.models.jpa.entities.UserRoleMappingEntity</class>
<exclude-unlisted-classes>true</exclude-unlisted-classes>
<properties>
<property name="hibernate.hbm2ddl.auto" value="create" />
<property name="hibernate.show_sql" value="false" />
<property name="hibernate.format_sql" value="false" />
<property name="hibernate.hbm2ddl.auto" value="update" />
</properties>
</persistence-unit>
</persistence>

View file

@ -21,6 +21,10 @@
<async-supported>true</async-supported>
</servlet>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
</welcome-file-list>
<filter>
<filter-name>Keycloak Session Management</filter-name>
<filter-class>org.keycloak.services.filters.KeycloakSessionServletFilter</filter-class>

View file

@ -31,14 +31,15 @@
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-model-picketlink</artifactId>
<artifactId>keycloak-model-jpa</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-model-jpa</artifactId>
<artifactId>keycloak-model-picketlink</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<!--<dependency>
@ -57,31 +58,6 @@
<artifactId>jboss-logging</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.picketlink</groupId>
<artifactId>picketlink-idm-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.picketlink</groupId>
<artifactId>picketlink-common</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.picketlink</groupId>
<artifactId>picketlink-idm-impl</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.picketlink</groupId>
<artifactId>picketlink-idm-simple-schema</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.picketlink</groupId>
<artifactId>picketlink-config</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-jaxrs</artifactId>
@ -156,21 +132,6 @@
<artifactId>jackson-xc</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.picketlink</groupId>
<artifactId>picketlink-common</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongo-java-driver</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>de.flapdoodle.embed</groupId>
<artifactId>de.flapdoodle.embed.mongo</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>javase</artifactId>
@ -183,7 +144,7 @@
<dependency>
<groupId>org.hibernate.javax.persistence</groupId>
<artifactId>hibernate-jpa-2.0-api</artifactId>
<scope>provided</scope>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.h2database</groupId>

View file

@ -1,5 +1,6 @@
package org.keycloak.services.managers;
import org.jboss.resteasy.logging.Logger;
import org.keycloak.models.ApplicationModel;
import org.keycloak.models.Constants;
import org.keycloak.models.KeycloakSession;
@ -17,12 +18,15 @@ import java.util.UUID;
*/
public class ApplianceBootstrap {
public void initKeycloakAdminRealm(RealmModel realm) {
}
private static final Logger logger = Logger.getLogger(ApplianceBootstrap.class);
public void bootstrap(KeycloakSession session) {
if (session.getRealm(Constants.ADMIN_REALM) != null) {
return;
}
logger.info("Initializing " + Constants.ADMIN_REALM + " realm");
RealmManager manager = new RealmManager(session);
RealmModel realm = manager.createRealm(Constants.ADMIN_REALM, Constants.ADMIN_REALM);
realm.setName(Constants.ADMIN_REALM);
@ -37,7 +41,6 @@ public class ApplianceBootstrap {
realm.setCookieLoginAllowed(true);
realm.setRegistrationAllowed(false);
manager.generateRealmKeys(realm);
initKeycloakAdminRealm(realm);
ApplicationModel adminConsole = realm.addApplication(Constants.ADMIN_CONSOLE_APPLICATION);
adminConsole.setEnabled(true);
@ -59,8 +62,6 @@ public class ApplianceBootstrap {
adminUser.addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD);
adminConsole.grantRole(adminUser, adminRole);
}
}
}

View file

@ -102,7 +102,6 @@ public class ApplicationManager {
}
}
}
if (resourceRep.isUseRealmMappings()) realm.addScopeMapping(applicationModel.getApplicationUser(), "*");
return applicationModel;
}

View file

@ -68,7 +68,6 @@ public class RealmManager {
public RealmModel createRealm(String id, String name) {
RealmModel realm = identitySession.createRealm(id, name);
realm.setName(name);
realm.addRole(Constants.WILDCARD_ROLE);
realm.addRole(Constants.APPLICATION_ROLE);
realm.addRole(Constants.IDENTITY_REQUESTER_ROLE);
return realm;
@ -129,7 +128,7 @@ public class RealmManager {
}
private void enableAccountManagement(RealmModel realm) {
ApplicationModel application = realm.getApplicationById(Constants.ACCOUNT_APPLICATION);
ApplicationModel application = realm.getApplicationNameMap().get(Constants.ACCOUNT_APPLICATION);
if (application == null) {
application = realm.addApplication(Constants.ACCOUNT_APPLICATION);
application.addDefaultRole(Constants.ACCOUNT_PROFILE_ROLE);
@ -224,6 +223,14 @@ public class RealmManager {
}
}
if (rep.getClients() != null) {
for (UserRepresentation clientRep : rep.getClients()) {
UserModel client = createUser(newRealm, clientRep);
newRealm.grantRole(client, newRealm.getRole(Constants.IDENTITY_REQUESTER_ROLE));
userMap.put(client.getLoginName(), client);
}
}
if (rep.getRoles() != null) {
for (RoleRepresentation roleRep : rep.getRoles()) {
createRole(newRealm, roleRep);
@ -237,7 +244,10 @@ public class RealmManager {
}
if (rep.getApplications() != null) {
createApplications(rep, newRealm);
Map<String, ApplicationModel> appMap = createApplications(rep, newRealm);
for (ApplicationModel app : appMap.values()) {
userMap.put(app.getApplicationUser().getLoginName(), app.getApplicationUser());
}
}
if (rep.getRoleMappings() != null) {
@ -398,12 +408,15 @@ public class RealmManager {
}
protected void createApplications(RealmRepresentation rep, RealmModel realm) {
protected Map<String, ApplicationModel> createApplications(RealmRepresentation rep, RealmModel realm) {
Map<String, ApplicationModel> appMap = new HashMap<String, ApplicationModel>();
RoleModel loginRole = realm.getRole(Constants.APPLICATION_ROLE);
ApplicationManager manager = new ApplicationManager(this);
for (ApplicationRepresentation resourceRep : rep.getApplications()) {
manager.createApplication(realm, loginRole, resourceRep);
ApplicationModel app = manager.createApplication(realm, loginRole, resourceRep);
appMap.put(app.getName(), app);
}
return appMap;
}
public static UserRepresentation toRepresentation(UserModel user) {

View file

@ -46,58 +46,42 @@ public class TokenManager {
public AccessCodeEntry createAccessCode(String scopeParam, String state, String redirect, RealmModel realm, UserModel client, UserModel user) {
boolean applicationResource = realm.hasRole(client, realm.getRole(Constants.APPLICATION_ROLE));
AccessCodeEntry code = new AccessCodeEntry();
SkeletonKeyScope scopeMap = null;
if (scopeParam != null) scopeMap = decodeScope(scopeParam);
List<RoleModel> realmRolesRequested = code.getRealmRolesRequested();
MultivaluedMap<String, RoleModel> resourceRolesRequested = code.getResourceRolesRequested();
Set<String> realmMapping = realm.getRoleMappingValues(user);
realmMapping.addAll(realm.getDefaultRoles());
if (realmMapping != null && realmMapping.size() > 0 && (scopeMap == null || scopeMap.containsKey("realm"))) {
Set<String> scope = realm.getScopeMappingValues(client);
if (scope.size() > 0) {
Set<String> scopeRequest = null;
if (scopeMap != null) {
if (scopeRequest == null) {
scopeRequest = new HashSet<String>();
}
scopeRequest.addAll(scopeMap.get("realm"));
if (scopeRequest.contains(Constants.WILDCARD_ROLE)) scopeRequest = null;
}
Set<String> scopeRequest = scopeMap != null ? new HashSet<String>(scopeMap.get("realm")) : null;
for (String role : realmMapping) {
if (
(scopeRequest == null || scopeRequest.contains(role)) &&
(scope.contains("*") || scope.contains(role))
)
if ((scopeRequest == null || scopeRequest.contains(role)) && scope.contains(role))
realmRolesRequested.add(realm.getRole(role));
}
}
}
for (ApplicationModel resource : realm.getApplications()) {
if (applicationResource && resource.getApplicationUser().getLoginName().equals(client.getLoginName())) {
resourceRolesRequested.addAll(resource.getName(), resource.getRoles());
} else {
Set<String> mapping = resource.getRoleMappingValues(user);
mapping.addAll(resource.getDefaultRoles());
if (mapping != null && mapping.size() > 0 && (scopeMap == null || scopeMap.containsKey(resource.getName()))) {
Set<String> scope = resource.getScopeMappingValues(client);
if (scope.size() > 0) {
Set<String> scopeRequest = null;
if (scopeMap != null) {
if (scopeRequest == null) {
scopeRequest = new HashSet<String>();
}
scopeRequest.addAll(scopeMap.get(resource.getName()));
if (scopeRequest.contains(Constants.WILDCARD_ROLE)) scopeRequest = null;
}
Set<String> scopeRequest = scopeMap != null ? new HashSet<String>(scopeMap.get(resource.getName())) : null;
for (String role : mapping) {
if (
(scopeRequest == null || scopeRequest.contains(role)) &&
(scope.contains("*") || scope.contains(role))
)
if ((scopeRequest == null || scopeRequest.contains(role)) && scope.contains(role))
resourceRolesRequested.add(resource.getName(), resource.getRole(role));
}
}
}
}
}
createToken(code, realm, client, user);

View file

@ -373,7 +373,8 @@ public class AccountService {
UserModel client = auth.getClient();
if (realm.hasRole(client, Constants.APPLICATION_ROLE)) {
// Tokens from cookies don't have roles
if (hasRole(client, Constants.ACCOUNT_MANAGE_ROLE) || (role != null && hasRole(client, role))) {
UserModel user = auth.getUser();
if (hasRole(user, Constants.ACCOUNT_MANAGE_ROLE) || (role != null && hasRole(user, role))) {
return true;
}
}
@ -389,9 +390,6 @@ public class AccountService {
}
private boolean hasRole(UserModel user, String role) {
if (application.getDefaultRoles().contains(role)) {
return true;
}
return application.hasRole(user, role);
}

View file

@ -1,18 +1,19 @@
package org.keycloak.services.resources;
import org.jboss.resteasy.logging.Logger;
import org.keycloak.SkeletonKeyContextResolver;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.ModelProvider;
import org.keycloak.services.managers.SocialRequestManager;
import org.keycloak.services.managers.TokenManager;
import org.keycloak.services.utils.PropertiesManager;
import javax.annotation.PreDestroy;
import javax.servlet.ServletContext;
import javax.ws.rs.core.Application;
import javax.ws.rs.core.Context;
import java.lang.reflect.Constructor;
import java.util.HashSet;
import java.util.Iterator;
import java.util.ServiceLoader;
import java.util.Set;
/**
@ -21,14 +22,19 @@ import java.util.Set;
*/
public class KeycloakApplication extends Application {
private static final Logger log = Logger.getLogger(KeycloakApplication.class);
private static final String MODEL_PROVIDER = "keycloak.model";
private static final String DEFAULT_MODEL_PROVIDER = "jpa";
protected Set<Object> singletons = new HashSet<Object>();
protected Set<Class<?>> classes = new HashSet<Class<?>>();
protected KeycloakSessionFactory factory;
public KeycloakApplication(@Context ServletContext context) {
KeycloakSessionFactory f = createSessionFactory();
this.factory = f;
this.factory = createSessionFactory();
context.setAttribute(KeycloakSessionFactory.class.getName(), factory);
//classes.add(KeycloakSessionCleanupFilter.class);
@ -41,57 +47,36 @@ public class KeycloakApplication extends Application {
classes.add(QRCodeResource.class);
}
protected KeycloakSessionFactory createSessionFactory() {
return buildSessionFactory();
}
public static KeycloakSessionFactory createSessionFactory() {
ServiceLoader<ModelProvider> providers = ServiceLoader.load(ModelProvider.class);
String configuredProvider = System.getProperty(MODEL_PROVIDER);
ModelProvider provider = null;
public static KeycloakSessionFactory buildSessionFactory() {
if (PropertiesManager.isMongoSessionFactory()) {
return buildMongoDBSessionFactory();
} else if (PropertiesManager.isPicketlinkSessionFactory()) {
return buildPicketlinkSessionFactory();
} else if (PropertiesManager.isJpaSessionFactory()) {
return buildJpaSessionFactory();
if (configuredProvider != null) {
for (ModelProvider p : providers) {
if (p.getId().equals(configuredProvider)) {
provider = p;
}
}
} else {
throw new IllegalStateException("Unknown session factory type: " + PropertiesManager.getSessionFactoryType());
for (ModelProvider p : providers) {
if (provider == null) {
provider = p;
}
if (p.getId().equals(DEFAULT_MODEL_PROVIDER)) {
provider = p;
break;
}
}
}
private static KeycloakSessionFactory buildJpaSessionFactory() {
ModelProvider provider = null;
try {
provider = (ModelProvider)Class.forName("org.keycloak.models.jpa.JpaModelProvider").newInstance();
} catch (Exception e) {
throw new RuntimeException(e);
}
if (provider != null) {
log.debug("Model provider: " + provider.getId());
return provider.createFactory();
}
private static KeycloakSessionFactory buildPicketlinkSessionFactory() {
ModelProvider provider = null;
try {
provider = (ModelProvider)Class.forName("org.keycloak.models.picketlink.PicketlinkModelProvider").newInstance();
} catch (Exception e) {
throw new RuntimeException(e);
}
return provider.createFactory();
}
private static KeycloakSessionFactory buildMongoDBSessionFactory() {
String host = PropertiesManager.getMongoHost();
int port = PropertiesManager.getMongoPort();
String dbName = PropertiesManager.getMongoDbName();
boolean dropDatabaseOnStartup = PropertiesManager.dropDatabaseOnStartup();
// Create MongoDBSessionFactory via reflection now
try {
Class<? extends KeycloakSessionFactory> mongoDBSessionFactoryClass = (Class<? extends KeycloakSessionFactory>)Class.forName("org.keycloak.models.mongo.keycloak.adapters.MongoDBSessionFactory");
Constructor<? extends KeycloakSessionFactory> constr = mongoDBSessionFactoryClass.getConstructor(String.class, int.class, String.class, boolean.class);
return constr.newInstance(host, port, dbName, dropDatabaseOnStartup);
} catch (Exception e) {
throw new RuntimeException(e);
}
throw new RuntimeException("Model provider not found");
}
public KeycloakSessionFactory getFactory() {
@ -103,9 +88,6 @@ public class KeycloakApplication extends Application {
factory.close();
}
@Override
public Set<Class<?>> getClasses() {
return classes;

View file

@ -263,7 +263,7 @@ public class SaasService {
@Path("login")
@GET
@NoCache
public Response loginPage() {
public Response loginPage(@QueryParam("path") String path) {
logger.debug("loginPage ********************** <---");
RealmManager realmManager = new RealmManager(session);
RealmModel realm = getAdminstrationRealm(realmManager);
@ -277,7 +277,7 @@ public class SaasService {
URI redirectUri = uriInfo.getBaseUriBuilder().path(SaasService.class).path(SaasService.class, "loginRedirect").build();
logger.debug("redirectUri: {0}", redirectUri.toString());
oauth.setStateCookiePath(redirectUri.getPath());
return oauth.redirect(uriInfo, redirectUri.toString());
return oauth.redirect(uriInfo, redirectUri.toString(), path);
}
@Path("login-redirect")
@ -316,7 +316,7 @@ public class SaasService {
logger.debug("state not specified");
throw new BadRequestException();
}
new JaxrsOAuthClient().checkStateCookie(uriInfo, headers);
String path = new JaxrsOAuthClient().checkStateCookie(uriInfo, headers);
JWSInput input = new JWSInput(code, providers);
boolean verifiedCode = false;
@ -358,7 +358,12 @@ public class SaasService {
}
logger.debug("loginRedirect SUCCESS");
NewCookie cookie = authManager.createSaasIdentityCookie(realm, accessCode.getUser(), uriInfo);
return Response.status(302).cookie(cookie).location(contextRoot(uriInfo).path(adminPath).build()).build();
URI redirectUri = contextRoot(uriInfo).path(adminPath).build();
if (path != null) {
redirectUri = redirectUri.resolve("#" + path);
}
return Response.status(302).cookie(cookie).location(redirectUri).build();
} finally {
authManager.expireCookie(AbstractOAuthClient.OAUTH_TOKEN_REQUEST_STATE, uriInfo.getAbsolutePath().getPath());
}

View file

@ -8,6 +8,7 @@ import org.jboss.resteasy.jwt.JsonSerialization;
import org.jboss.resteasy.logging.Logger;
import org.jboss.resteasy.spi.HttpRequest;
import org.jboss.resteasy.spi.HttpResponse;
import org.keycloak.models.ApplicationModel;
import org.keycloak.models.Constants;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakTransaction;
@ -323,6 +324,17 @@ public class TokenService {
realm.updateCredential(user, credentials);
}
for (String r : realm.getDefaultRoles()) {
realm.grantRole(user, realm.getRole(r));
}
for (ApplicationModel application : realm.getApplications()) {
for (String r : application.getDefaultRoles()) {
application.grantRole(user, application.getRole(r));
}
}
return null;
}
@ -423,7 +435,7 @@ public class TokenService {
logger.debug("accessRequest SUCCESS");
AccessTokenResponse res = accessTokenResponse(realm.getPrivateKey(), accessCode.getToken());
return Cors.add(request, Response.ok(res)).allowedOrigins(client).build();
return Cors.add(request, Response.ok(res)).allowedOrigins(client).allowedMethods("POST").build();
}
protected AccessTokenResponse accessTokenResponse(PrivateKey privateKey, SkeletonKeyToken token) {

View file

@ -1,6 +1,7 @@
package org.keycloak.services.resources.admin;
import org.jboss.resteasy.annotations.cache.NoCache;
import org.keycloak.models.Constants;
import org.keycloak.models.RoleContainerModel;
import org.keycloak.models.RoleModel;
import org.keycloak.representations.idm.RoleRepresentation;
@ -39,10 +40,12 @@ public class RoleContainerResource {
List<RoleModel> roleModels = roleContainer.getRoles();
List<RoleRepresentation> roles = new ArrayList<RoleRepresentation>();
for (RoleModel roleModel : roleModels) {
if (!roleModel.getName().startsWith(Constants.INTERNAL_ROLE)) {
RoleRepresentation role = new RoleRepresentation(roleModel.getName(), roleModel.getDescription());
role.setId(roleModel.getId());
roles.add(role);
}
}
return roles;
}
@ -52,7 +55,7 @@ public class RoleContainerResource {
@Produces("application/json")
public RoleRepresentation getRole(final @PathParam("id") String id) {
RoleModel roleModel = roleContainer.getRoleById(id);
if (roleModel == null) {
if (roleModel == null || roleModel.getName().startsWith(Constants.INTERNAL_ROLE)) {
throw new NotFoundException();
}
RoleRepresentation rep = new RoleRepresentation(roleModel.getName(), roleModel.getDescription());
@ -65,7 +68,7 @@ public class RoleContainerResource {
@Consumes("application/json")
public void updateRole(final @PathParam("id") String id, final RoleRepresentation rep) {
RoleModel role = roleContainer.getRoleById(id);
if (role == null) {
if (role == null || role.getName().startsWith(Constants.INTERNAL_ROLE)) {
throw new NotFoundException();
}
role.setName(rep.getName());
@ -76,7 +79,7 @@ public class RoleContainerResource {
@POST
@Consumes("application/json")
public Response createRole(final @Context UriInfo uriInfo, final RoleRepresentation rep) {
if (roleContainer.getRole(rep.getName()) != null) {
if (roleContainer.getRole(rep.getName()) != null || rep.getName().startsWith(Constants.INTERNAL_ROLE)) {
throw new InternalServerErrorException(); // todo appropriate status here.
}
RoleModel role = roleContainer.addRole(rep.getName());

View file

@ -3,6 +3,7 @@ package org.keycloak.services.resources.admin;
import org.jboss.resteasy.annotations.cache.NoCache;
import org.jboss.resteasy.logging.Logger;
import org.keycloak.models.ApplicationModel;
import org.keycloak.models.Constants;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
@ -66,10 +67,12 @@ public class UsersResource {
user.setEmail(rep.getEmail());
user.setFirstName(rep.getFirstName());
user.setLastName(rep.getLastName());
if (rep.getAttributes() != null) {
for (Map.Entry<String, String> attr : rep.getAttributes().entrySet()) {
user.setAttribute(attr.getKey(), attr.getValue());
}
}
}
@POST
@Consumes("application/json")
@ -98,12 +101,19 @@ public class UsersResource {
@Produces("application/json")
public UserRepresentation getUser(final @PathParam("username") String username) {
UserModel user = realm.getUser(username);
if (user == null) {
if (user == null || !isUser(user)) {
throw new NotFoundException();
}
return new RealmManager(session).toRepresentation(user);
}
@Path("{username}")
@DELETE
@NoCache
public void deleteUser(final @PathParam("username") String username) {
realm.deleteUser(username);
}
@GET
@NoCache
@Produces("application/json")
@ -117,8 +127,10 @@ public class UsersResource {
if (search != null) {
List<UserModel> userModels = manager.searchUsers(search, realm);
for (UserModel user : userModels) {
if (isUser(user)) {
results.add(manager.toRepresentation(user));
}
}
} else {
Map<String, String> attributes = new HashMap<String, String>();
if (last != null) {
@ -142,6 +154,10 @@ public class UsersResource {
return results;
}
private boolean isUser(UserModel user) {
return !realm.hasRole(user, realm.getRole(Constants.IDENTITY_REQUESTER_ROLE)) && !realm.hasRole(user, realm.getRole(Constants.APPLICATION_ROLE));
}
@Path("{username}/role-mappings")
@GET
@Produces("application/json")

View file

@ -26,34 +26,34 @@ package org.keycloak.services.resources.flows;
*/
public class Pages {
public final static String ACCESS = "/forms/access.ftl";
public final static String ACCESS = "access.ftl";
public final static String ACCOUNT = "/forms/account.ftl";
public final static String ACCOUNT = "account.ftl";
public final static String LOGIN = "/forms/login.ftl";
public final static String LOGIN = "login.ftl";
public final static String LOGIN_TOTP = "/forms/login-totp.ftl";
public final static String LOGIN_TOTP = "login-totp.ftl";
public final static String LOGIN_CONFIG_TOTP = "/forms/login-config-totp.ftl";
public final static String LOGIN_CONFIG_TOTP = "login-config-totp.ftl";
public final static String LOGIN_VERIFY_EMAIL = "/forms/login-verify-email.ftl";
public final static String LOGIN_VERIFY_EMAIL = "login-verify-email.ftl";
public final static String OAUTH_GRANT = "/forms/login-oauth-grant.ftl";
public final static String OAUTH_GRANT = "login-oauth-grant.ftl";
public final static String PASSWORD = "/forms/password.ftl";
public final static String PASSWORD = "password.ftl";
public final static String LOGIN_RESET_PASSWORD = "/forms/login-reset-password.ftl";
public final static String LOGIN_RESET_PASSWORD = "login-reset-password.ftl";
public final static String LOGIN_UPDATE_PASSWORD = "/forms/login-update-password.ftl";
public final static String LOGIN_UPDATE_PASSWORD = "login-update-password.ftl";
public final static String REGISTER = "/forms/register.ftl";
public final static String REGISTER = "register.ftl";
public final static String ERROR = "/forms/error.ftl";
public final static String ERROR = "error.ftl";
public final static String SOCIAL = "/forms/social.ftl";
public final static String SOCIAL = "social.ftl";
public final static String TOTP = "/forms/totp.ftl";
public final static String TOTP = "totp.ftl";
public final static String LOGIN_UPDATE_PROFILE = "/forms/login-update-profile.ftl";
public final static String LOGIN_UPDATE_PROFILE = "login-update-profile.ftl";
}

View file

@ -4,11 +4,13 @@ import org.junit.Assert;
import org.junit.FixMethodOrder;
import org.junit.Test;
import org.junit.runners.MethodSorters;
import org.keycloak.models.ApplicationModel;
import org.keycloak.models.Constants;
import org.keycloak.models.OAuthClientModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RequiredCredentialModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.SocialLinkModel;
import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserModel;
import org.keycloak.representations.idm.CredentialRepresentation;
@ -173,6 +175,34 @@ public class AdapterTest extends AbstractKeycloakTest {
}
@Test
public void deleteUser() throws Exception {
test1CreateRealm();
UserModel user = realmModel.addUser("bburke");
user.setAttribute("attr1", "val1");
user.addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD);
RoleModel testRole = realmModel.addRole("test");
realmModel.grantRole(user, testRole);
ApplicationModel app = realmModel.addApplication("test-app");
RoleModel appRole = app.addRole("test");
app.grantRole(user, appRole);
SocialLinkModel socialLink = new SocialLinkModel("google", user.getLoginName());
realmModel.addSocialLink(user, socialLink);
UserCredentialModel cred = new UserCredentialModel();
cred.setType(CredentialRepresentation.PASSWORD);
cred.setValue("password");
realmModel.updateCredential(user, cred);
Assert.assertTrue(realmModel.deleteUser("bburke"));
Assert.assertFalse(realmModel.deleteUser("bburke"));
Assert.assertNull(realmModel.getUser("bburke"));
}
@Test
public void testUserSearch() throws Exception {
test1CreateRealm();
@ -199,6 +229,15 @@ public class AdapterTest extends AbstractKeycloakTest {
Assert.assertEquals(bburke.getEmail(), "bburke@redhat.com");
}
{
List<UserModel> userModels = adapter.searchUsers("bil burk", realmModel);
Assert.assertEquals(userModels.size(), 1);
UserModel bburke = userModels.get(0);
Assert.assertEquals(bburke.getFirstName(), "Bill");
Assert.assertEquals(bburke.getLastName(), "Burke");
Assert.assertEquals(bburke.getEmail(), "bburke@redhat.com");
}
{
List<UserModel> userModels = adapter.searchUsers("bburke@redhat.com", realmModel);
@ -209,6 +248,15 @@ public class AdapterTest extends AbstractKeycloakTest {
Assert.assertEquals(bburke.getEmail(), "bburke@redhat.com");
}
{
List<UserModel> userModels = adapter.searchUsers("rke@redhat.com", realmModel);
Assert.assertEquals(userModels.size(), 1);
UserModel bburke = userModels.get(0);
Assert.assertEquals(bburke.getFirstName(), "Bill");
Assert.assertEquals(bburke.getLastName(), "Burke");
Assert.assertEquals(bburke.getEmail(), "bburke@redhat.com");
}
{
List<UserModel> userModels = adapter.searchUsers("bburke", realmModel);
Assert.assertEquals(userModels.size(), 1);
@ -218,6 +266,15 @@ public class AdapterTest extends AbstractKeycloakTest {
Assert.assertEquals(bburke.getEmail(), "bburke@redhat.com");
}
{
List<UserModel> userModels = adapter.searchUsers("BurK", realmModel);
Assert.assertEquals(userModels.size(), 1);
UserModel bburke = userModels.get(0);
Assert.assertEquals(bburke.getFirstName(), "Bill");
Assert.assertEquals(bburke.getLastName(), "Burke");
Assert.assertEquals(bburke.getEmail(), "bburke@redhat.com");
}
{
List<UserModel> userModels = adapter.searchUsers("Burke", realmModel);
Assert.assertEquals(userModels.size(), 1);
@ -293,7 +350,7 @@ public class AdapterTest extends AbstractKeycloakTest {
realmModel.addRole("admin");
realmModel.addRole("user");
List<RoleModel> roles = realmModel.getRoles();
Assert.assertEquals(6, roles.size());
Assert.assertEquals(5, roles.size());
UserModel user = realmModel.addUser("bburke");
RoleModel role = realmModel.getRole("user");
realmModel.grantRole(user, role);

Some files were not shown because too many files have changed in this diff Show more