keycloak-memoizeit
Changes
admin-ui/src/main/resources/META-INF/resources/admin/lib/angular/ui-bootstrap-tpls-0.4.0.js 4(+2 -2)
admin-ui-styles/src/main/resources/META-INF/resources/admin-ui/css/img/login-register-social.png 0(+0 -0)
admin-ui-styles/src/main/resources/META-INF/resources/admin-ui/css/img/login-register-social.svg 19(+19 -0)
dist/assembly.xml 50(+50 -0)
dist/build.xml 37(+37 -0)
dist/pom.xml 96(+96 -0)
forms/src/main/resources/META-INF/resources/forms/theme/default/css/img/login-register-separator.png 0(+0 -0)
forms/src/main/resources/META-INF/resources/forms/theme/default/css/img/login-register-separators.png 0(+0 -0)
forms/src/main/resources/META-INF/resources/forms/theme/default/css/img/login-register-social.png 0(+0 -0)
forms/src/main/resources/META-INF/resources/forms/theme/default/css/img/login-register-social-separator.png 0(+0 -0)
forms/src/main/resources/META-INF/resources/forms/theme/default/css/img/login-register-social-separators.png 0(+0 -0)
model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ApplicationAdapter.java 15(+15 -0)
model/picketlink/src/main/java/org/keycloak/models/picketlink/mappings/ApplicationData.java 10(+10 -0)
model/picketlink/src/main/java/org/keycloak/models/picketlink/mappings/ApplicationEntity.java 12(+12 -0)
model/pom.xml 2(+1 -1)
pom.xml 4(+3 -1)
server/pom.xml 149(+149 -0)
server/src/main/webapp/WEB-INF/web.xml 39(+39 -0)
testsuite/integration/pom.xml 4(+4 -0)
testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionMultipleActionsTest.java 2(+1 -1)
Details
diff --git a/admin-ui/src/main/resources/META-INF/resources/admin/index.html b/admin-ui/src/main/resources/META-INF/resources/admin/index.html
index b953e92..9487218 100755
--- a/admin-ui/src/main/resources/META-INF/resources/admin/index.html
+++ b/admin-ui/src/main/resources/META-INF/resources/admin/index.html
@@ -5,12 +5,6 @@
<meta charset="utf-8">
<title>Keycloak</title>
- <style type="text/css">
- #idletimeout { background:#CC5100; border:3px solid #FF6500; color:#fff; font-family:arial, sans-serif; text-align:center; font-size:12px; padding:10px; position:relative; top:0px; left:0; right:0; z-index:100000; display:none; }
- #idletimeout a { color:#fff; font-weight:bold }
- #idletimeout span { font-weight:bold }
- </style>
-
<link rel="icon" href="/auth-server/admin-ui/img/favicon.ico">
<!-- Frameworks -->
@@ -59,7 +53,7 @@
<body class="admin-console" data-ng-controller="GlobalCtrl">
<div id="idletimeout">
- You will be logged off in <span></span> seconds due to inactivity.
+ You will be logged off in <strong><span></span> seconds</strong> due to inactivity.
<a id="idletimeout-resume" href="#">Click here to continue using this web page</a>.
</div>
diff --git a/admin-ui/src/main/resources/META-INF/resources/admin/js/app.js b/admin-ui/src/main/resources/META-INF/resources/admin/js/app.js
index 4c8d2c2..7025d2e 100755
--- a/admin-ui/src/main/resources/META-INF/resources/admin/js/app.js
+++ b/admin-ui/src/main/resources/META-INF/resources/admin/js/app.js
@@ -4,29 +4,31 @@ var module = angular.module('keycloak', [ 'keycloak.services', 'keycloak.loaders
var resourceRequests = 0;
module.config([ '$routeProvider', function($routeProvider) {
-
- $routeProvider
-
- .when('/create/realm', {
- templateUrl : 'partials/realm-detail.html',
- resolve : {
- realm : function(RealmLoader) {
- return {};
- }
- },
- controller : 'RealmDetailCtrl'
- }).when('/realms/:realm', {
- templateUrl : 'partials/realm-detail.html',
- resolve : {
- realm : function(RealmLoader) {
- return RealmLoader();
- }
- },
- controller : 'RealmDetailCtrl'
- }).when('/realms', {
- templateUrl : 'partials/realm-list.html',
- controller : 'RealmListCtrl'
- }).when('/realms/:realm/token-settings', {
+
+ $routeProvider
+ .when('/create/realm', {
+ templateUrl : 'partials/realm-detail.html',
+ resolve : {
+ realm : function(RealmLoader) {
+ return {};
+ }
+ },
+ controller : 'RealmDetailCtrl'
+ })
+ .when('/realms/:realm', {
+ templateUrl : 'partials/realm-detail.html',
+ resolve : {
+ realm : function(RealmLoader) {
+ return RealmLoader();
+ }
+ },
+ controller : 'RealmDetailCtrl'
+ })
+ .when('/realms', {
+ templateUrl : 'partials/realm-list.html',
+ controller : 'RealmListCtrl'
+ })
+ .when('/realms/:realm/token-settings', {
templateUrl : 'partials/realm-tokens.html',
resolve : {
realm : function(RealmLoader) {
@@ -34,7 +36,8 @@ module.config([ '$routeProvider', function($routeProvider) {
}
},
controller : 'RealmTokenDetailCtrl'
- }).when('/realms/:realm/social-settings', {
+ })
+ .when('/realms/:realm/social-settings', {
templateUrl : 'partials/realm-social.html',
resolve : {
realm : function(RealmLoader) {
@@ -52,18 +55,28 @@ module.config([ '$routeProvider', function($routeProvider) {
},
controller : 'RealmRequiredCredentialsCtrl'
})
- .when('/create/user/:realm', {
- templateUrl : 'partials/user-detail.html',
- resolve : {
- realm : function(RealmLoader) {
- return RealmLoader();
- },
- user : function() {
- return {};
- }
- },
- controller : 'UserDetailCtrl'
- }).when('/realms/:realm/users/:user', {
+ .when('/realms/:realm/smtp-settings', {
+ templateUrl : 'partials/realm-smtp.html',
+ resolve : {
+ realm : function(RealmLoader) {
+ return RealmLoader();
+ }
+ },
+ controller : 'RealmSMTPSettingsCtrl'
+ })
+ .when('/create/user/:realm', {
+ templateUrl : 'partials/user-detail.html',
+ resolve : {
+ realm : function(RealmLoader) {
+ return RealmLoader();
+ },
+ user : function() {
+ return {};
+ }
+ },
+ controller : 'UserDetailCtrl'
+ })
+ .when('/realms/:realm/users/:user', {
templateUrl : 'partials/user-detail.html',
resolve : {
realm : function(RealmLoader) {
@@ -74,7 +87,8 @@ module.config([ '$routeProvider', function($routeProvider) {
}
},
controller : 'UserDetailCtrl'
- }).when('/realms/:realm/users/:user/role-mappings', {
+ })
+ .when('/realms/:realm/users/:user/role-mappings', {
templateUrl : 'partials/role-mappings.html',
resolve : {
realm : function(RealmLoader) {
@@ -91,15 +105,16 @@ module.config([ '$routeProvider', function($routeProvider) {
}
},
controller : 'UserRoleMappingCtrl'
- }).when('/realms/:realm/users', {
- templateUrl : 'partials/user-list.html',
- resolve : {
- realm : function(RealmLoader) {
- return RealmLoader();
- }
- },
- controller : 'UserListCtrl'
- })
+ })
+ .when('/realms/:realm/users', {
+ templateUrl : 'partials/user-list.html',
+ resolve : {
+ realm : function(RealmLoader) {
+ return RealmLoader();
+ }
+ },
+ controller : 'UserListCtrl'
+ })
.when('/create/role/:realm', {
templateUrl : 'partials/role-detail.html',
@@ -112,7 +127,8 @@ module.config([ '$routeProvider', function($routeProvider) {
}
},
controller : 'RoleDetailCtrl'
- }).when('/realms/:realm/roles/:role', {
+ })
+ .when('/realms/:realm/roles/:role', {
templateUrl : 'partials/role-detail.html',
resolve : {
realm : function(RealmLoader) {
@@ -123,7 +139,8 @@ module.config([ '$routeProvider', function($routeProvider) {
}
},
controller : 'RoleDetailCtrl'
- }).when('/realms/:realm/roles', {
+ })
+ .when('/realms/:realm/roles', {
templateUrl : 'partials/role-list.html',
resolve : {
realm : function(RealmLoader) {
@@ -150,7 +167,8 @@ module.config([ '$routeProvider', function($routeProvider) {
}
},
controller : 'ApplicationRoleDetailCtrl'
- }).when('/realms/:realm/applications/:application/roles/:role', {
+ })
+ .when('/realms/:realm/applications/:application/roles/:role', {
templateUrl : 'partials/application-role-detail.html',
resolve : {
realm : function(RealmLoader) {
@@ -164,7 +182,8 @@ module.config([ '$routeProvider', function($routeProvider) {
}
},
controller : 'ApplicationRoleDetailCtrl'
- }).when('/realms/:realm/applications/:application/credentials', {
+ })
+ .when('/realms/:realm/applications/:application/credentials', {
templateUrl : 'partials/application-credentials.html',
resolve : {
realm : function(RealmLoader) {
@@ -175,7 +194,8 @@ module.config([ '$routeProvider', function($routeProvider) {
}
},
controller : 'ApplicationCredentialsCtrl'
- }).when('/realms/:realm/applications/:application/roles', {
+ })
+ .when('/realms/:realm/applications/:application/roles', {
templateUrl : 'partials/application-role-list.html',
resolve : {
realm : function(RealmLoader) {
@@ -189,7 +209,8 @@ module.config([ '$routeProvider', function($routeProvider) {
}
},
controller : 'ApplicationRoleListCtrl'
- }).when('/realms/:realm/applications/:application/scope-mappings', {
+ })
+ .when('/realms/:realm/applications/:application/scope-mappings', {
templateUrl : 'partials/application-scope-mappings.html',
resolve : {
realm : function(RealmLoader) {
@@ -208,8 +229,6 @@ module.config([ '$routeProvider', function($routeProvider) {
controller : 'ApplicationScopeMappingCtrl'
})
-
-
.when('/create/application/:realm', {
templateUrl : 'partials/application-detail.html',
resolve : {
@@ -224,7 +243,8 @@ module.config([ '$routeProvider', function($routeProvider) {
}
},
controller : 'ApplicationDetailCtrl'
- }).when('/realms/:realm/applications/:application', {
+ })
+ .when('/realms/:realm/applications/:application', {
templateUrl : 'partials/application-detail.html',
resolve : {
realm : function(RealmLoader) {
@@ -238,7 +258,8 @@ module.config([ '$routeProvider', function($routeProvider) {
}
},
controller : 'ApplicationDetailCtrl'
- }).when('/realms/:realm/applications', {
+ })
+ .when('/realms/:realm/applications', {
templateUrl : 'partials/application-list.html',
resolve : {
realm : function(RealmLoader) {
@@ -250,33 +271,33 @@ module.config([ '$routeProvider', function($routeProvider) {
},
controller : 'ApplicationListCtrl'
})
- .otherwise({
- templateUrl : 'partials/home.html'
- });
+ .otherwise({
+ templateUrl : 'partials/home.html'
+ });
} ]);
module.config(function($httpProvider) {
- $httpProvider.responseInterceptors.push('errorInterceptor');
+ $httpProvider.responseInterceptors.push('errorInterceptor');
- var spinnerFunction = function(data, headersGetter) {
- if (resourceRequests == 0) {
- $('#loading').show();
- }
- resourceRequests++;
- return data;
- };
- $httpProvider.defaults.transformRequest.push(spinnerFunction);
+ var spinnerFunction = function(data, headersGetter) {
+ if (resourceRequests == 0) {
+ $('#loading').show();
+ }
+ resourceRequests++;
+ return data;
+ };
+ $httpProvider.defaults.transformRequest.push(spinnerFunction);
- $httpProvider.responseInterceptors.push('spinnerInterceptor');
+ $httpProvider.responseInterceptors.push('spinnerInterceptor');
});
module.factory('errorInterceptor', function($q, $window, $rootScope, $location, Auth) {
- return function(promise) {
- return promise.then(function(response) {
- $rootScope.httpProviderError = null;
- return response;
- }, function(response) {
+ return function(promise) {
+ return promise.then(function(response) {
+ $rootScope.httpProviderError = null;
+ return response;
+ }, function(response) {
if (response.status == 401) {
console.log('session timeout?');
Auth.loggedIn = false;
@@ -284,28 +305,28 @@ module.factory('errorInterceptor', function($q, $window, $rootScope, $location,
} else {
$rootScope.httpProviderError = response.status;
}
- return $q.reject(response);
- });
- };
+ return $q.reject(response);
+ });
+ };
});
module.factory('spinnerInterceptor', function($q, $window, $rootScope, $location) {
- return function(promise) {
- return promise.then(function(response) {
- resourceRequests--;
- if (resourceRequests == 0) {
- $('#loading').hide();
- }
- return response;
- }, function(response) {
- resourceRequests--;
- if (resourceRequests == 0) {
- $('#loading').hide();
- }
-
- return $q.reject(response);
- });
- };
+ return function(promise) {
+ return promise.then(function(response) {
+ resourceRequests--;
+ if (resourceRequests == 0) {
+ $('#loading').hide();
+ }
+ return response;
+ }, function(response) {
+ resourceRequests--;
+ if (resourceRequests == 0) {
+ $('#loading').hide();
+ }
+
+ return $q.reject(response);
+ });
+ };
});
// collapsable form fieldsets
@@ -354,80 +375,80 @@ module.directive('collapsed', function() {
module.directive('kcInput', function() {
- var d = {
- scope : true,
- replace : false,
- link : function(scope, element, attrs) {
- var form = element.children('form');
- var label = element.children('label');
- var input = element.children('input');
-
- var id = form.attr('name') + '.' + input.attr('name');
-
- element.attr('class', 'control-group');
-
- label.attr('class', 'control-label');
- label.attr('for', id);
-
- input.wrap('<div class="controls"/>');
- input.attr('id', id);
-
- if (!input.attr('placeHolder')) {
- input.attr('placeHolder', label.text());
- }
-
- if (input.attr('required')) {
- label.append(' <span class="required">*</span>');
- }
- }
- };
- return d;
+ var d = {
+ scope : true,
+ replace : false,
+ link : function(scope, element, attrs) {
+ var form = element.children('form');
+ var label = element.children('label');
+ var input = element.children('input');
+
+ var id = form.attr('name') + '.' + input.attr('name');
+
+ element.attr('class', 'control-group');
+
+ label.attr('class', 'control-label');
+ label.attr('for', id);
+
+ input.wrap('<div class="controls"/>');
+ input.attr('id', id);
+
+ if (!input.attr('placeHolder')) {
+ input.attr('placeHolder', label.text());
+ }
+
+ if (input.attr('required')) {
+ label.append(' <span class="required">*</span>');
+ }
+ }
+ };
+ return d;
});
module.directive('kcEnter', function() {
- return function(scope, element, attrs) {
- element.bind("keydown keypress", function(event) {
- if (event.which === 13) {
- scope.$apply(function() {
- scope.$eval(attrs.kcEnter);
- });
-
- event.preventDefault();
- }
- });
- };
+ return function(scope, element, attrs) {
+ element.bind("keydown keypress", function(event) {
+ if (event.which === 13) {
+ scope.$apply(function() {
+ scope.$eval(attrs.kcEnter);
+ });
+
+ event.preventDefault();
+ }
+ });
+ };
});
module.filter('remove', function() {
- return function(input, remove, attribute) {
- if (!input || !remove) {
- return input;
- }
-
- var out = [];
- for ( var i = 0; i < input.length; i++) {
- var e = input[i];
-
- for (var j = 0; j < remove.length; j++) {
- if (attribute) {
- if (remove[j][attribute] == e[attribute]) {
- e = null;
- break;
- }
- } else {
- if (remove[j] == e) {
- e = null;
- break;
- }
- }
- }
-
- if (e != null) {
- out.push(e);
- }
- }
-
- return out;
- };
+ return function(input, remove, attribute) {
+ if (!input || !remove) {
+ return input;
+ }
+
+ var out = [];
+ for ( var i = 0; i < input.length; i++) {
+ var e = input[i];
+
+ for (var j = 0; j < remove.length; j++) {
+ if (attribute) {
+ if (remove[j][attribute] == e[attribute]) {
+ e = null;
+ break;
+ }
+ } else {
+ if (remove[j] == e) {
+ e = null;
+ break;
+ }
+ }
+ }
+
+ if (e != null) {
+ out.push(e);
+ }
+ }
+
+ return out;
+ };
});
\ No newline at end of file
diff --git a/admin-ui/src/main/resources/META-INF/resources/admin/js/controllers/realm.js b/admin-ui/src/main/resources/META-INF/resources/admin/js/controllers/realm.js
index 1b4a4f5..fa11a54 100755
--- a/admin-ui/src/main/resources/META-INF/resources/admin/js/controllers/realm.js
+++ b/admin-ui/src/main/resources/META-INF/resources/admin/js/controllers/realm.js
@@ -453,3 +453,38 @@ 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,
+ smtpServer: realm.smtpServer
+ };
+
+ var oldCopy = angular.copy($scope.realm);
+ $scope.changed = false;
+
+ $scope.$watch('realm', function() {
+ if (!angular.equals($scope.realm, oldCopy)) {
+ $scope.changed = true;
+ }
+ }, true);
+
+ $scope.save = function() {
+ if ($scope.realmForm.$valid) {
+ var realmCopy = angular.copy($scope.realm);
+ $scope.changed = false;
+ Realm.update(realmCopy, function () {
+ $location.url("/realms/" + realm.id + "/smtp-settings");
+ Notifications.success("Your changes have been saved to the realm.");
+ });
+ } else {
+ $scope.realmForm.showErrors = true;
+ }
+ };
+
+ $scope.reset = function() {
+ $scope.realm = angular.copy(oldCopy);
+ $scope.changed = false;
+ $scope.realmForm.showErrors = false;
+ };
+});
\ No newline at end of file
diff --git a/admin-ui/src/main/resources/META-INF/resources/admin/js/services.js b/admin-ui/src/main/resources/META-INF/resources/admin/js/services.js
index 043e686..4ee4a90 100755
--- a/admin-ui/src/main/resources/META-INF/resources/admin/js/services.js
+++ b/admin-ui/src/main/resources/META-INF/resources/admin/js/services.js
@@ -16,15 +16,16 @@ module.service('Auth', function() {
module.service('Dialog', function($dialog) {
var dialog = {};
dialog.confirmDelete = function(name, type, success) {
- var title = 'Delete ' + name;
- var msg = 'Are you sure you want to permanently delete this ' + type + '?';
+ 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 btns = [ {
result : 'cancel',
label : 'Cancel'
}, {
result : 'ok',
- label : 'Delete this ' + type,
- cssClass : 'btn-primary'
+ label : 'Delete',
+ cssClass : 'destructive'
} ];
$dialog.messageBox(title, msg, btns).open().then(function(result) {
diff --git a/admin-ui/src/main/resources/META-INF/resources/admin/lib/angular/ui-bootstrap-tpls-0.4.0.js b/admin-ui/src/main/resources/META-INF/resources/admin/lib/angular/ui-bootstrap-tpls-0.4.0.js
index b587abd..9b62fe7 100755
--- a/admin-ui/src/main/resources/META-INF/resources/admin/lib/angular/ui-bootstrap-tpls-0.4.0.js
+++ b/admin-ui/src/main/resources/META-INF/resources/admin/lib/angular/ui-bootstrap-tpls-0.4.0.js
@@ -3004,10 +3004,10 @@ angular.module("template/dialog/message.html", []).run(["$templateCache", functi
" <h3>{{ title }}</h3>\n" +
"</div>\n" +
"<div class=\"modal-body\">\n" +
- " <p>{{ message }}</p>\n" +
+ " <p ng-bind-html-unsafe=\"message\"></p>\n" +
"</div>\n" +
"<div class=\"modal-footer\">\n" +
- " <button ng-repeat=\"btn in buttons\" ng-click=\"close(btn.result)\" class=\"btn\" ng-class=\"btn.cssClass\">{{ btn.label }}</button>\n" +
+ " <button ng-repeat=\"btn in buttons\" ng-click=\"close(btn.result)\" class=\"\" ng-class=\"btn.cssClass\">{{ btn.label }}</button> \n" +
"</div>\n" +
"</div>\n" +
"</div>\n" +
diff --git a/admin-ui/src/main/resources/META-INF/resources/admin/partials/menu.html b/admin-ui/src/main/resources/META-INF/resources/admin/partials/menu.html
index 4ae72a7..d62d21d 100755
--- a/admin-ui/src/main/resources/META-INF/resources/admin/partials/menu.html
+++ b/admin-ui/src/main/resources/META-INF/resources/admin/partials/menu.html
@@ -1,7 +1,7 @@
<div class="header rcue">
<div class="navbar utility">
<div class="navbar-inner clearfix container">
- <h1><a href="#"><strong>Keycloak</strong> Central Login</a></h1>
+ <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/registrations">Register</a></li>
diff --git a/admin-ui/src/main/resources/META-INF/resources/admin/partials/realm-credentials.html b/admin-ui/src/main/resources/META-INF/resources/admin/partials/realm-credentials.html
index 1d4d9ff..0f9f849 100755
--- a/admin-ui/src/main/resources/META-INF/resources/admin/partials/realm-credentials.html
+++ b/admin-ui/src/main/resources/META-INF/resources/admin/partials/realm-credentials.html
@@ -9,6 +9,7 @@
<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>
+ <li><a href="#/realms/{{realm.id}}/smtp-settings">SMTP</a></li>
</ul>
</div>
<div id="content">
diff --git a/admin-ui/src/main/resources/META-INF/resources/admin/partials/realm-detail.html b/admin-ui/src/main/resources/META-INF/resources/admin/partials/realm-detail.html
index 1567a2f..d35d8cd 100755
--- a/admin-ui/src/main/resources/META-INF/resources/admin/partials/realm-detail.html
+++ b/admin-ui/src/main/resources/META-INF/resources/admin/partials/realm-detail.html
@@ -9,6 +9,7 @@
<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">
@@ -76,6 +77,32 @@
</div>
</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>
+ </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>
+ </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"
diff --git a/admin-ui/src/main/resources/META-INF/resources/admin/partials/realm-smtp.html b/admin-ui/src/main/resources/META-INF/resources/admin/partials/realm-smtp.html
new file mode 100755
index 0000000..6abd4ee
--- /dev/null
+++ b/admin-ui/src/main/resources/META-INF/resources/admin/partials/realm-smtp.html
@@ -0,0 +1,134 @@
+<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><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 class="active"><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">SMTP Configuration</li>
+ </ol>
+ <h2><span>{{realm.realm}}</span> SMTP Settings</h2>
+ <form name="realmForm" novalidate>
+ <fieldset>
+ <legend uncollapsed><span class="text">Required Settings</span></legend>
+ <div class="form-group clearfix">
+ <label for="smtpHost" class="control-label">Host <span class="required">*</span></label>
+ <div class="controls">
+ <input id="smtpHost" type="text" ng-model="realm.smtpServer.host" placeholder="SMTP Host" required>
+ </div>
+ </div>
+ <div class="form-group clearfix">
+ <label for="smtpPort" class="control-label">Port <span class="required">*</span></label>
+ <div class="controls">
+ <input id="smtpPort" type="text" ng-model="realm.smtpServer.port" placeholder="SMTP Port (defaults to 25)" required>
+ </div>
+ </div>
+ <div class="form-group clearfix">
+ <label for="smtpFrom" class="control-label">From <span class="required">*</span></label>
+ <div class="controls">
+ <input id="smtpFrom" type="email" ng-model="realm.smtpServer.from" placeholder="SMTP From" required>
+ </div>
+ </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>
+ </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>
+ </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>
+ </div>
+ <div class="form-group clearfix">
+ <label for="smtpUsername" class="control-label">Username <span class="required" ng-show="realm.smtpServer.auth">*</span></label>
+ <div class="controls">
+ <input id="smtpUsername" type="text" ng-model="realm.smtpServer.user" placeholder="Login Username" ng-disabled="!realm.smtpServer.auth" ng-required="realm.smtpServer.auth">
+ </div>
+ </div>
+ <div class="form-group clearfix">
+ <label for="smtpPassword" class="control-label">Password <span class="required" ng-show="realm.smtpServer.auth">*</span></label>
+ <div class="controls">
+ <input id="smtpPassword" type="password" ng-model="realm.smtpServer.password" placeholder="Login Password" ng-disabled="!realm.smtpServer.auth" ng-required="realm.smtpServer.auth">
+ </div>
+ </div>
+ </fieldset>
+<!--
+ <fieldset class="border-top">
+ <div class="form-group clearfix" ng-repeat="(name, setting) in smtpSettings | orderBy: setting.required">
+ <label for="{{name}}" class="control-label">{{name.replace('mail.smtp.','')}} <span class="required" data-ng-show="setting.required">*</span></label>
+
+ <div ng-show="setting.type == 'boolean'" class="onoffswitch">
+ <input type="checkbox" data-ng-model="realm.smtp[name]" class="onoffswitch-checkbox" name="{{name}}" id="{{name}}">
+ <label for="{{name}}" 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>
+ <div ng-show="setting.type == 'String'" class="controls">
+ <input id="{{name}}" type="text" ng-model="realm.smtp[name]" placeholder="SMTP {{name}} / {{ setting.required }}">
+ </div>
+ <div ng-show="setting.type == 'int'" class="controls">
+ <input id="{{name}}" type="number" ng-model="realm.smtp[name]" placeholder="SMTP {{name}} / {{ setting.required }}">
+ </div>
+ </div>
+ </fieldset>
+-->
+ <div class="form-actions">
+ <button type="submit" data-ng-click="save()" class="primary" data-ng-show="changed">Save
+ </button>
+ <button type="submit" data-ng-click="reset()" data-ng-show="changed">Clear changes
+ </button>
+ </div>
+
+ </form>
+ </div>
+ </div>
+ <div id="container-right-bg"></div>
+ </div>
+</div>
\ No newline at end of file
diff --git a/admin-ui/src/main/resources/META-INF/resources/admin/partials/realm-social.html b/admin-ui/src/main/resources/META-INF/resources/admin/partials/realm-social.html
index a102740..a046cc5 100755
--- a/admin-ui/src/main/resources/META-INF/resources/admin/partials/realm-social.html
+++ b/admin-ui/src/main/resources/META-INF/resources/admin/partials/realm-social.html
@@ -9,6 +9,7 @@
<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">
@@ -92,7 +93,7 @@
<div ng-include src="'partials/provider/'+ helpPId +'-help.html'"></div>
</div>
<div class="modal-footer">
- <button class="btn" ng-click="closeHelp()">Close</button>
+ <button ng-click="closeHelp()">Close</button>
</div>
</div>
</div>
diff --git a/admin-ui/src/main/resources/META-INF/resources/admin/partials/realm-tokens.html b/admin-ui/src/main/resources/META-INF/resources/admin/partials/realm-tokens.html
index b87cf6d..4092a90 100755
--- a/admin-ui/src/main/resources/META-INF/resources/admin/partials/realm-tokens.html
+++ b/admin-ui/src/main/resources/META-INF/resources/admin/partials/realm-tokens.html
@@ -9,6 +9,7 @@
<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>
+ <li><a href="#/realms/{{realm.id}}/smtp-settings">SMTP</a></li>
</ul>
</div>
<div id="content">
@@ -49,7 +50,7 @@
</div>
</div>
<div class="form-group input-select">
- <label for="accessCodeLifespanUserAction">Access code user action lifespan</label>
+ <label for="accessCodeLifespanUserAction" class="two-lines">Access code user action lifespan</label>
<div class="input-group">
<input type="text" data-ng-model="realm.accessCodeLifespanUserAction" id="accessCodeLifespanUserAction" name="accessCodeLifespanUserAction" class="tiny">
<div class="select-rcue">
diff --git a/admin-ui/src/main/resources/META-INF/resources/admin/partials/role-detail.html b/admin-ui/src/main/resources/META-INF/resources/admin/partials/role-detail.html
index 78eeda7..e9ee0c0 100755
--- a/admin-ui/src/main/resources/META-INF/resources/admin/partials/role-detail.html
+++ b/admin-ui/src/main/resources/META-INF/resources/admin/partials/role-detail.html
@@ -9,6 +9,7 @@
<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>
+ <li><a href="#/realms/{{realm.id}}/smtp-settings">SMTP</a></li>
</ul>
</div>
<div id="content">
diff --git a/admin-ui/src/main/resources/META-INF/resources/admin/partials/role-list.html b/admin-ui/src/main/resources/META-INF/resources/admin/partials/role-list.html
index 69fbdcd..b1ae532 100755
--- a/admin-ui/src/main/resources/META-INF/resources/admin/partials/role-list.html
+++ b/admin-ui/src/main/resources/META-INF/resources/admin/partials/role-list.html
@@ -9,6 +9,7 @@
<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>
+ <li><a href="#/realms/{{realm.id}}/smtp-settings">SMTP</a></li>
</ul>
</div>
<div id="content">
diff --git a/admin-ui-styles/src/main/resources/META-INF/resources/admin-ui/css/admin-console.css b/admin-ui-styles/src/main/resources/META-INF/resources/admin-ui/css/admin-console.css
index c339f2f..bf52b71 100644
--- a/admin-ui-styles/src/main/resources/META-INF/resources/admin-ui/css/admin-console.css
+++ b/admin-ui-styles/src/main/resources/META-INF/resources/admin-ui/css/admin-console.css
@@ -43,7 +43,34 @@ body {
background-image: url(img/feedback-info-sign.png);
background-color: #e4f3fa;
}
+#idletimeout {
+ background: #CC5100;
+ color: #FFFFFF;
+ font-size: 1.1em;
+ padding: 0.90909090909091em;
+ text-align: center;
+ display: none;
+}
+#idletimeout a {
+ color: #fff;
+ font-weight: bold;
+}
+.loading span {
+ background: url(img/loader.gif) no-repeat center top;
+ position: fixed;
+ z-index: 1000;
+ top: 50%;
+ left: 50%;
+ margin-top: -2.27272727272727em;
+ margin-left: -2.27272727272727em;
+ padding-top: 2.90909090909091em;
+ font-size: 1.1em;
+ color: #666;
+}
/* Header */
+.header.rcue {
+ z-index: 50;
+}
.header.rcue .navbar.utility {
background-color: #393F45;
border-bottom: 1px solid #53565B;
@@ -65,36 +92,6 @@ body {
min-height: 42px;
max-width: 1170px;
}
-.header.rcue .navbar.primary .select-rcue {
- font-size: 0.76923076923077em;
- margin-left: 1em;
- margin-top: 0.7em;
- display: inline-block;
- vertical-align: middle;
- background-color: #555a5e;
- background-image: none;
- background-image: url(img/sprite-arrow-down.png);
- background-repeat: no-repeat;
- background-position: right -26px;
- border: 1px solid #676c6e;
- border-radius: 2px;
- padding-left: 0;
-}
-.header.rcue .navbar.primary .select-rcue:hover {
- border-color: #7e8385;
-}
-.header.rcue .navbar.primary .select-rcue select {
- color: #fff;
-}
-.header.rcue .navbar.primary .select-rcue select:-moz-focusring {
- color: transparent;
- text-shadow: 0 0 0 #fff;
-}
-.header.rcue .navbar.primary .select-rcue select option {
- background-color: #fff;
- color: #333;
- padding: 0.36363636363636em 0.90909090909091em;
-}
.header.rcue .navbar.primary .nav > li {
/*
.dropdown {
@@ -215,7 +212,6 @@ body {
.header.rcue .navbar.primary .nav > li .select-rcue select option {
background-color: #fff;
color: #333;
- padding: 0.36363636363636em 0.90909090909091em;
}
.header.rcue .navbar.primary .nav > li a#refresh {
border: none;
@@ -469,6 +465,42 @@ table + .feedback.inline.warning {
td.token-cell button {
margin-top: -1px;
}
+/* Modal boxes */
+.modal .modal-dialog {
+ padding-top: 10em;
+}
+.modal .modal-content {
+ box-shadow: none;
+ border-radius: 0;
+ border: 1px solid #666;
+}
+.modal .modal-header {
+ background-color: #f8f8f8;
+ padding: 1.5em 2em;
+ border-bottom: none;
+}
+.modal .modal-header h3 {
+ font-size: 1.3em;
+ font-weight: bold;
+ font-family: "Open Sans", sans-serif;
+ margin: 0;
+}
+.modal .modal-body p {
+ font-size: 1.1em;
+}
+.modal .modal-body p.primary {
+ font-size: 1.1em;
+ font-weight: bold;
+ margin-bottom: 0.45454545454545em;
+}
+.modal .modal-footer {
+ border-top: 0;
+}
+.modal .modal-footer .primary,
+.modal .modal-footer .destructive {
+ float: right;
+ margin-left: 0.90909090909091em;
+}
/* Page: User Account */
.user form fieldset div:first-child {
margin-top: 1em;
diff --git a/admin-ui-styles/src/main/resources/META-INF/resources/admin-ui/css/admin-console.less b/admin-ui-styles/src/main/resources/META-INF/resources/admin-ui/css/admin-console.less
index 4cca051..7ef77be 100644
--- a/admin-ui-styles/src/main/resources/META-INF/resources/admin-ui/css/admin-console.less
+++ b/admin-ui-styles/src/main/resources/META-INF/resources/admin-ui/css/admin-console.less
@@ -60,11 +60,43 @@ body {
}
}
+#idletimeout {
+ background: #CC5100;
+ color: #FFFFFF;
+ font-size: 1.1em;
+ padding: 0.90909090909091em;
+ text-align: center;
+ display: none;
+
+ a {
+ color: #fff;
+ font-weight: bold;
+ }
+}
+
+.loading {
+
+ span {
+ background: url(img/loader.gif) no-repeat center top;
+ position: fixed;
+ z-index: 1000;
+ top: 50%;
+ left: 50%;
+ margin-top: -2.27272727272727em;
+ margin-left: -2.27272727272727em;
+ padding-top: 2.90909090909091em;
+ font-size: 1.1em;
+ color: #666;
+ }
+}
+
/* Header */
.header.rcue {
+ z-index: 50;
+
.navbar.utility {
background-color: #393F45;
border-bottom: 1px solid #53565B;
@@ -88,41 +120,6 @@ body {
min-height: 42px;
max-width: 1170px;
}
-
- .select-rcue {
- font-size: 0.76923076923077em;
- margin-left: 1em;
- margin-top: 0.7em;
- display: inline-block;
- vertical-align: middle;
- background-color: #555a5e;
- background-image: none;
- background-image: url(img/sprite-arrow-down.png);
- background-repeat: no-repeat;
- background-position: right -26px;
- border: 1px solid #676c6e;
- border-radius: 2px;
- padding-left: 0;
-
- &:hover {
- border-color: #7e8385;
- }
-
- select {
- color: #fff;
-
- &:-moz-focusring {
- color: transparent;
- text-shadow: 0 0 0 #fff;
- }
-
- option {
- background-color: #fff;
- color: #333;
- padding: 0.36363636363636em 0.90909090909091em;
- }
- }
- }
.nav > li {
@@ -165,7 +162,6 @@ body {
option {
background-color: #fff;
color: #333;
- padding: 0.36363636363636em 0.90909090909091em;
}
}
}
@@ -567,6 +563,59 @@ td.token-cell button {
}
+/* Modal boxes */
+
+.modal {
+
+ .modal-dialog {
+ padding-top: 10em;
+ }
+
+ .modal-content {
+ box-shadow: none;
+ border-radius: 0;
+ border: 1px solid #666;
+ }
+
+ .modal-header {
+ background-color: #f8f8f8;
+ padding: 1.5em 2em;
+ border-bottom: none;
+
+ h3 {
+ font-size: 1.3em;
+ font-weight: bold;
+ font-family: @open-sans;
+ margin: 0;
+ }
+ }
+
+ .modal-body {
+
+ p {
+ font-size: 1.1em;
+
+ &.primary {
+ font-size: 1.1em;
+ font-weight: bold;
+ margin-bottom: 0.45454545454545em;
+ }
+ }
+ }
+
+ .modal-footer {
+ border-top: 0;
+
+ .primary,
+ .destructive {
+ float: right;
+ margin-left: 0.90909090909091em;
+ }
+
+ }
+}
+
+
/* Page: User Account */
.user {
diff --git a/admin-ui-styles/src/main/resources/META-INF/resources/admin-ui/css/forms.css b/admin-ui-styles/src/main/resources/META-INF/resources/admin-ui/css/forms.css
index 8d99830..d83a677 100644
--- a/admin-ui-styles/src/main/resources/META-INF/resources/admin-ui/css/forms.css
+++ b/admin-ui-styles/src/main/resources/META-INF/resources/admin-ui/css/forms.css
@@ -16,12 +16,14 @@ input[type="email"],
textarea {
font-size: 1.1em;
padding: 0 0.545454545454545em;
- min-width: 18.1818181818182em;
height: 2.36363636363636em;
+ /* 26px */
+
border: 1px #b6b6b6 solid;
border-radius: 2px;
box-shadow: inset 0px 2px 2px rgba(0, 0, 0, 0.1);
color: #333;
+ width: 18.1818em;
}
input[type="text"]:hover,
input[type="password"]:hover,
@@ -51,6 +53,36 @@ input[type="email"].error:focus,
textarea.error:focus {
box-shadow: 0 0 5px #ba1212;
}
+input[type="text"].tiny,
+input[type="password"].tiny,
+input[type="email"].tiny,
+textarea.tiny {
+ width: 4.54545454545455em;
+}
+input[type="text"].small,
+input[type="password"].small,
+input[type="email"].small,
+textarea.small {
+ width: 9.09090909090909em;
+}
+input[type="text"].medium,
+input[type="password"].medium,
+input[type="email"].medium,
+textarea.medium {
+ width: 18.1818em;
+}
+input[type="text"].large,
+input[type="password"].large,
+input[type="email"].large,
+textarea.large {
+ width: 27.2727272727273em;
+}
+input[type="text"].xlarge,
+input[type="password"].xlarge,
+input[type="email"].xlarge,
+textarea.xlarge {
+ width: 36.3636363636364em;
+}
textarea {
padding: 0.45em 0.545454545454545em;
height: auto;
@@ -252,7 +284,7 @@ button.primary:focus,
.search-comp .icon-search {
position: absolute;
right: 0.2em;
- top: 0.4em;
+ top: 0.6em;
opacity: 0.5;
filter: alpha(opacity=50);
}
@@ -457,6 +489,8 @@ fieldset.border-top {
.form-group .required {
font-size: 1.1em;
color: #CB2915;
+ display: inline-block;
+ margin-top: -0.09090909090909em;
}
legend + .form-group {
padding-top: 1em;
@@ -550,7 +584,7 @@ input[type="email"].tiny {
}
.select-rcue,
.select2-container .select2-choice {
- height: 26px;
+ height: 2.6em;
border: 1px #b6b6b6 solid;
border-radius: 2px;
box-shadow: inset 0px 2px 2px rgba(0, 0, 0, 0.1);
@@ -593,7 +627,7 @@ input[type="email"].tiny {
}
.select-rcue option {
line-height: 2em;
- padding-left: 0.90909090909091em;
+ padding: 0.363636em 0.909091em;
}
.select-rcue option:hover {
background-color: #d5ecf9;
@@ -788,7 +822,8 @@ input[type="email"].tiny {
margin-top: 3em;
margin-bottom: 5em;
}
-.form-actions .primary {
+.form-actions .primary,
+.form-actions .destructive {
float: right;
margin-left: 0.90909090909091em;
}
diff --git a/admin-ui-styles/src/main/resources/META-INF/resources/admin-ui/css/forms.less b/admin-ui-styles/src/main/resources/META-INF/resources/admin-ui/css/forms.less
index c965f0e..5fc4c0b 100644
--- a/admin-ui-styles/src/main/resources/META-INF/resources/admin-ui/css/forms.less
+++ b/admin-ui-styles/src/main/resources/META-INF/resources/admin-ui/css/forms.less
@@ -23,12 +23,12 @@ input[type="email"],
textarea {
font-size: 1.1em;
padding: 0 0.545454545454545em;
- min-width: 18.1818181818182em;
- height: 2.36363636363636em;
+ height: 2.36363636363636em; /* 26px */
border: 1px #b6b6b6 solid;
border-radius: 2px;
box-shadow: inset 0px 2px 2px rgba(0,0,0,0.1);
color: #333;
+ width: 18.1818em;
&:hover {
border-color: #62afdb;
@@ -49,6 +49,26 @@ textarea {
box-shadow: 0 0 5px #ba1212;
}
}
+
+ &.tiny {
+ width: 4.54545454545455em;
+ }
+
+ &.small {
+ width: 9.09090909090909em;
+ }
+
+ &.medium {
+ width: 18.1818em;
+ }
+
+ &.large {
+ width: 27.2727272727273em;
+ }
+
+ &.xlarge {
+ width: 36.3636363636364em
+ }
}
textarea {
@@ -280,7 +300,7 @@ button.primary,
.icon-search {
position: absolute;
right: 0.2em;
- top: 0.4em;
+ top: 0.6em;
opacity: 0.5;
filter: alpha(opacity=50);
@@ -532,6 +552,8 @@ fieldset.border-top {
.required {
font-size: 1.1em;
color: #CB2915;
+ display: inline-block;
+ margin-top: -0.09090909090909em;
}
}
@@ -656,7 +678,7 @@ input[type="email"] {
.select-rcue,
.select2-container .select2-choice {
- height: 26px;
+ height: 2.6em;
border: 1px #b6b6b6 solid;
border-radius: 2px;
box-shadow: inset 0px 2px 2px rgba(0,0,0,0.1);
@@ -707,7 +729,7 @@ input[type="email"] {
option {
line-height: 2em;
- padding-left: 0.90909090909091em;
+ padding: 0.363636em 0.909091em;
&:hover {
background-color: #d5ecf9;
@@ -940,7 +962,8 @@ input[type="email"] {
margin-top: 3em;
margin-bottom: 5em;
- .primary {
+ .primary,
+ .destructive {
float: right;
margin-left: 0.90909090909091em;
}
diff --git a/admin-ui-styles/src/main/resources/META-INF/resources/admin-ui/css/img/loader.gif b/admin-ui-styles/src/main/resources/META-INF/resources/admin-ui/css/img/loader.gif
new file mode 100644
index 0000000..06ef39f
Binary files /dev/null and b/admin-ui-styles/src/main/resources/META-INF/resources/admin-ui/css/img/loader.gif differ
diff --git a/admin-ui-styles/src/main/resources/META-INF/resources/admin-ui/css/img/login-register-social.png b/admin-ui-styles/src/main/resources/META-INF/resources/admin-ui/css/img/login-register-social.png
new file mode 100644
index 0000000..e86d738
Binary files /dev/null and b/admin-ui-styles/src/main/resources/META-INF/resources/admin-ui/css/img/login-register-social.png differ
diff --git a/admin-ui-styles/src/main/resources/META-INF/resources/admin-ui/css/img/login-register-social.svg b/admin-ui-styles/src/main/resources/META-INF/resources/admin-ui/css/img/login-register-social.svg
new file mode 100644
index 0000000..d28ebcc
--- /dev/null
+++ b/admin-ui-styles/src/main/resources/META-INF/resources/admin-ui/css/img/login-register-social.svg
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
+<svg version="1.0" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ width="12px" height="400px" viewBox="0 0 12 400" enable-background="new 0 0 12 400" xml:space="preserve">
+<rect x="6.001" opacity="0.07" fill="#FFFFFF" enable-background="new " width="0.997" height="190"/>
+<rect x="6" y="209" opacity="0.07" fill="#FFFFFF" enable-background="new " width="1" height="191"/>
+<g opacity="0.15">
+ <path fill="#FFFFFF" d="M6.501,200.066c0,1.047-0.264,1.864-0.791,2.452S4.454,203.4,3.524,203.4c-0.574,0-1.084-0.135-1.529-0.404
+ c-0.445-0.27-0.789-0.656-1.031-1.16c-0.242-0.504-0.363-1.094-0.363-1.77c0-1.047,0.262-1.862,0.785-2.446
+ c0.523-0.584,1.25-0.876,2.18-0.876c0.898,0,1.612,0.299,2.142,0.896C6.238,198.237,6.501,199.047,6.501,200.066z M1.608,200.066
+ c0,0.821,0.164,1.446,0.492,1.875s0.811,0.645,1.447,0.645c0.636,0,1.12-0.214,1.45-0.643c0.33-0.428,0.495-1.053,0.495-1.877
+ c0-0.816-0.165-1.437-0.495-1.86s-0.817-0.636-1.462-0.636c-0.637,0-1.117,0.209-1.441,0.627
+ C1.77,198.615,1.608,199.238,1.608,200.066z"/>
+ <path fill="#FFFFFF" d="M11.136,196.744c0.285,0,0.541,0.023,0.768,0.07l-0.135,0.902c-0.266-0.059-0.5-0.088-0.703-0.088
+ c-0.52,0-0.964,0.211-1.333,0.633c-0.369,0.422-0.554,0.947-0.554,1.576v3.445H8.206v-6.422h0.803l0.111,1.189h0.047
+ c0.238-0.418,0.525-0.74,0.861-0.967C10.364,196.855,10.733,196.744,11.136,196.744z"/>
+</g>
+</svg>
diff --git a/core/src/main/java/org/keycloak/representations/idm/ApplicationRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/ApplicationRepresentation.java
index 878092e..59347ca 100755
--- a/core/src/main/java/org/keycloak/representations/idm/ApplicationRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/ApplicationRepresentation.java
@@ -18,6 +18,7 @@ public class ApplicationRepresentation {
protected boolean enabled;
protected List<CredentialRepresentation> credentials;
protected List<RoleRepresentation> roles;
+ protected String[] defaultRoles;
protected List<UserRoleMappingRepresentation> roleMappings;
protected List<ScopeMappingRepresentation> scopeMappings;
protected List<String> redirectUris;
@@ -164,4 +165,12 @@ public class ApplicationRepresentation {
public void setWebOrigins(List<String> webOrigins) {
this.webOrigins = webOrigins;
}
+
+ public String[] getDefaultRoles() {
+ return defaultRoles;
+ }
+
+ public void setDefaultRoles(String[] defaultRoles) {
+ this.defaultRoles = defaultRoles;
+ }
}
diff --git a/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
index 65281dc..c520fd3 100755
--- a/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
@@ -28,7 +28,7 @@ public class RealmRepresentation {
protected String privateKey;
protected String publicKey;
protected List<RoleRepresentation> roles;
- protected String[] defaultRoles;
+ protected List<String> defaultRoles;
protected Set<String> requiredCredentials;
protected Set<String> requiredApplicationCredentials;
protected Set<String> requiredOAuthClientCredentials;
@@ -220,11 +220,11 @@ public class RealmRepresentation {
this.roles = roles;
}
- public String[] getDefaultRoles() {
+ public List<String> getDefaultRoles() {
return defaultRoles;
}
- public void setDefaultRoles(String[] defaultRoles) {
+ public void setDefaultRoles(List<String> defaultRoles) {
this.defaultRoles = defaultRoles;
}
dist/assembly.xml 50(+50 -0)
diff --git a/dist/assembly.xml b/dist/assembly.xml
new file mode 100644
index 0000000..4c892d1
--- /dev/null
+++ b/dist/assembly.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<assembly xmlns="urn:maven:assembly:1.1.0-SNAPSHOT">
+ <id>distro</id>
+
+ <formats>
+ <format>zip</format>
+ <format>tar.gz</format>
+ </formats>
+
+ <includeBaseDirectory>false</includeBaseDirectory>
+
+ <fileSets>
+ <fileSet>
+ <directory>${build.target.dir}</directory>
+ <outputDirectory>keycloak-${project.version}</outputDirectory>
+ <excludes>
+ <exclude>**/*.sh</exclude>
+ <exclude>domain/tmp/auth</exclude>
+ <exclude>domain/tmp/auth</exclude>
+ <exclude>**/*-users.properties</exclude>
+ </excludes>
+ </fileSet>
+ <fileSet>
+ <directory>${build.target.dir}</directory>
+ <outputDirectory>keycloak-${project.version}</outputDirectory>
+ <includes>
+ <include>**/*.sh</include>
+ </includes>
+ <fileMode>0755</fileMode>
+ </fileSet>
+ <fileSet>
+ <directory>${build.target.dir}</directory>
+ <outputDirectory>keycloak-${project.version}</outputDirectory>
+ <includes>
+ <include>**/*-users.properties</include>
+ </includes>
+ <fileMode>0600</fileMode>
+ </fileSet>
+ <fileSet>
+ <directory>${build.target.dir}</directory>
+ <outputDirectory>keycloak-${project.version}</outputDirectory>
+ <includes>
+ <include>domain/tmp/auth</include>
+ <include>standalone/tmp/auth</include>
+ </includes>
+ <directoryMode>0700</directoryMode>
+ </fileSet>
+ </fileSets>
+
+</assembly>
dist/build.xml 37(+37 -0)
diff --git a/dist/build.xml b/dist/build.xml
new file mode 100644
index 0000000..c9bfd95
--- /dev/null
+++ b/dist/build.xml
@@ -0,0 +1,37 @@
+<project name="keycloak-dist" basedir=".">
+ <target name="jboss">
+ <unzip src="${org.jboss.as:jboss-as-dist:zip}" dest="${project.build.directory}"/>
+ <chmod perm="755">
+ <fileset dir="${project.build.directory}/jboss-as-${jboss.version}/bin">
+ <include name="**/*.sh"/>
+ </fileset>
+ </chmod>
+ <move todir="${build.target.dir}" overwrite="true">
+ <fileset dir="${project.build.directory}/jboss-as-${jboss.version}">
+ <include name="**/*"/>
+ </fileset>
+ </move>
+ <delete dir="${project.build.directory}/jboss-as-${jboss.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"/>
+ <unzip src="${project.build.directory}/resteasy-jaxrs-${resteasy.version}-all.zip"
+ dest="${project.build.directory}">
+ <patternset>
+ <include name="resteasy-jaxrs-${resteasy.version}/resteasy-jboss-modules-${resteasy.version}.zip"/>
+ </patternset>
+ <mapper type="flatten"/>
+ </unzip>
+ <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"/>
+</project>
dist/pom.xml 96(+96 -0)
diff --git a/dist/pom.xml b/dist/pom.xml
new file mode 100644
index 0000000..ef0ff99
--- /dev/null
+++ b/dist/pom.xml
@@ -0,0 +1,96 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-parent</artifactId>
+ <version>1.0-alpha-1</version>
+ </parent>
+
+ <artifactId>keycloak-dist</artifactId>
+ <name>Keycloak Dist</name>
+ <packaging>pom</packaging>
+
+ <properties>
+ <build.target.dir>${project.build.directory}/keycloak-${project.version}</build.target.dir>
+ </properties>
+
+ <profiles>
+ <profile>
+ <id>release</id>
+ <activation>
+ <property>
+ <name>release</name>
+ <value>true</value>
+ </property>
+ </activation>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-server</artifactId>
+ <version>1.0-alpha-1</version>
+ <type>war</type>
+ </dependency>
+ <dependency>
+ <groupId>org.jboss.as</groupId>
+ <artifactId>jboss-as-dist</artifactId>
+ <version>${jboss.version}</version>
+ <type>zip</type>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-antrun-plugin</artifactId>
+ <version>1.7</version>
+ <executions>
+ <execution>
+ <id>build</id>
+ <phase>compile</phase>
+ <configuration>
+ <target>
+ <ant antfile="build.xml" inheritRefs="false">
+ <target name="all"/>
+ </ant>
+ </target>
+ </configuration>
+ <goals>
+ <goal>run</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-assembly-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>assemble</id>
+ <phase>package</phase>
+ <goals>
+ <goal>single</goal>
+ </goals>
+ <configuration>
+ <descriptors>
+ <descriptor>assembly.xml</descriptor>
+ </descriptors>
+ <finalName>keycloak-${project.version}</finalName>
+ <appendAssemblyId>false</appendAssemblyId>
+ <outputDirectory>target/</outputDirectory>
+ <workDirectory>target/assembly/work</workDirectory>
+ <tarLongFileMode>gnu</tarLongFileMode>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
+</project>
diff --git a/forms/src/main/resources/META-INF/resources/forms/theme/default/css/img/login-register-separator.png b/forms/src/main/resources/META-INF/resources/forms/theme/default/css/img/login-register-separator.png
index 5ea210c..d626a45 100644
Binary files a/forms/src/main/resources/META-INF/resources/forms/theme/default/css/img/login-register-separator.png and b/forms/src/main/resources/META-INF/resources/forms/theme/default/css/img/login-register-separator.png differ
diff --git a/forms/src/main/resources/META-INF/resources/forms/theme/default/css/img/login-register-social.png b/forms/src/main/resources/META-INF/resources/forms/theme/default/css/img/login-register-social.png
new file mode 100644
index 0000000..e86d738
Binary files /dev/null and b/forms/src/main/resources/META-INF/resources/forms/theme/default/css/img/login-register-social.png differ
diff --git a/forms/src/main/resources/META-INF/resources/forms/theme/default/css/img/login-register-social-separator.png b/forms/src/main/resources/META-INF/resources/forms/theme/default/css/img/login-register-social-separator.png
new file mode 100644
index 0000000..3f08929
Binary files /dev/null and b/forms/src/main/resources/META-INF/resources/forms/theme/default/css/img/login-register-social-separator.png differ
diff --git a/forms/src/main/resources/META-INF/resources/forms/theme/default/css/login-register.css b/forms/src/main/resources/META-INF/resources/forms/theme/default/css/login-register.css
index 8fa9b44..bc25029 100644
--- a/forms/src/main/resources/META-INF/resources/forms/theme/default/css/login-register.css
+++ b/forms/src/main/resources/META-INF/resources/forms/theme/default/css/login-register.css
@@ -49,13 +49,30 @@ body {
width: 100%;
min-width: 120em;
}
+.rcue-login-register .form-area.separator,
+.rcue-login-register .form-area.social,
+.rcue-login-register .form-area.social.separator {
+ background-repeat: no-repeat;
+ background-position: 42.7em center;
+}
+.rcue-login-register .form-area.separator {
+ background-image: url(img/login-register-separator.png);
+ background-position: 43.2em center;
+}
+.rcue-login-register .form-area.social {
+ background-image: url(img/login-register-social.png);
+}
+.rcue-login-register .form-area.social.separator {
+ background-image: url(img/login-register-social-separator.png);
+}
.rcue-login-register .background-area .section {
float: left;
padding: 0 4.5em 0 4.6em;
width: auto;
position: relative;
}
-.rcue-login-register .background-area .separator .section {
+.rcue-login-register .background-area .separator .section,
+.rcue-login-register .background-area .social .section {
padding-top: 1.5em;
padding-bottom: 1.5em;
}
@@ -65,15 +82,6 @@ body {
.rcue-login-register .background-area .section:first-child {
padding-right: 4.5em;
}
-.rcue-login-register .form-area.separator {
- background-image: url(img/login-register-separator.png);
- background-repeat: no-repeat;
- background-position: 43.2em center;
-}
-.rcue-login-register .form-area.social {
- background-image: url(img/login-register-social-separators.png);
- background-position: 39.6em center;
-}
.rcue-login-register .section > p {
font-size: 1.3em;
margin-bottom: 1.53846153846154em;
@@ -120,7 +128,6 @@ body {
.rcue-login-register form > div.aside-btn input[type="checkbox"] {
margin-bottom: 0.54545454545455em;
/* 6px */
-
}
.rcue-login-register form > input[type="button"],
.rcue-login-register form > input[type="submit"]{
@@ -137,13 +144,10 @@ body {
top: -0.636363636363636em;
}
.rcue-login-register .feedback.bottom-left {
- left: 32.7em;
+ left: 35.7em;
top: -9.2em;
min-width: 35em;
}
-.rcue-login-register.reset .feedback.bottom-left {
- left: 35.7em;
-}
.rcue-login-register input.error[type="text"],
.rcue-login-register input.error[type="password"],
.rcue-login-register input.error[type="email"] {
diff --git a/forms/src/main/resources/META-INF/resources/forms/theme/default/login.ftl b/forms/src/main/resources/META-INF/resources/forms/theme/default/login.ftl
index 2b24b4c..be4c793 100755
--- a/forms/src/main/resources/META-INF/resources/forms/theme/default/login.ftl
+++ b/forms/src/main/resources/META-INF/resources/forms/theme/default/login.ftl
@@ -39,4 +39,4 @@
</div>
</#if>
-</@layout.registrationLayout>
\ No newline at end of file
+</@layout.registrationLayout>
diff --git a/forms/src/main/resources/META-INF/resources/forms/theme/default/login-oauth-grant.ftl b/forms/src/main/resources/META-INF/resources/forms/theme/default/login-oauth-grant.ftl
index edc7ef7..85121de 100755
--- a/forms/src/main/resources/META-INF/resources/forms/theme/default/login-oauth-grant.ftl
+++ b/forms/src/main/resources/META-INF/resources/forms/theme/default/login-oauth-grant.ftl
@@ -18,18 +18,20 @@
<span>${role.description}</span>
</li>
</#list>
- </ul>
- <#list oauth.resourceRolesRequested?keys as resourceRole>
- <p class="instruction"><strong>${resourceRole}</strong> requests access to:</p>
- <ul>
- <#list oauth.resourceRolesRequested[resourceRole] as role>
- <li>
- <span>${role.description}</span>
- </li>
- </#list>
- </ul>
- </#list>
+ <#list oauth.resourceRolesRequested?keys as resourceRole>
+ <li>
+ <strong>${resourceRole}</strong>
+ <ul>
+ <#list oauth.resourceRolesRequested[resourceRole] as role>
+ <li>
+ <span><#if role.description??>${role.description}<#else>${role.name}</#if></span>
+ </li>
+ </#list>
+ </ul>
+ </li>
+ </#list>
+ </ul>
<p class="terms">Keycloak Central Login and Google will use this information in accordance with their respective terms of service and privacy policies.</p>
<form class="form-actions" action="${oauth.action}" method="POST">
diff --git a/model/api/src/main/java/org/keycloak/models/ApplicationModel.java b/model/api/src/main/java/org/keycloak/models/ApplicationModel.java
index efeccb8..e5374b8 100755
--- a/model/api/src/main/java/org/keycloak/models/ApplicationModel.java
+++ b/model/api/src/main/java/org/keycloak/models/ApplicationModel.java
@@ -1,5 +1,7 @@
package org.keycloak.models;
+import java.util.List;
+
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
@@ -31,4 +33,9 @@ public interface ApplicationModel extends RoleContainerModel, RoleMapperModel, S
void setBaseUrl(String url);
+ List<String> getDefaultRoles();
+
+ void addDefaultRole(String name);
+
+ void updateDefaultRoles(String[] defaultRoles);
}
diff --git a/model/api/src/main/java/org/keycloak/models/Constants.java b/model/api/src/main/java/org/keycloak/models/Constants.java
index b0af34c..b02c9c2 100755
--- a/model/api/src/main/java/org/keycloak/models/Constants.java
+++ b/model/api/src/main/java/org/keycloak/models/Constants.java
@@ -12,5 +12,9 @@ public interface Constants {
String IDENTITY_REQUESTER_ROLE = "KEYCLOAK_IDENTITY_REQUESTER";
String WILDCARD_ROLE = "*";
+ String ACCOUNT_APPLICATION = "Account";
+ String ACCOUNT_PROFILE_ROLE = "view-profile";
+ String ACCOUNT_MANAGE_ROLE = "manage-account";
+
String ACCOUNT_MANAGEMENT_APPLICATION = "Account Management";
}
diff --git a/model/api/src/main/java/org/keycloak/models/RealmModel.java b/model/api/src/main/java/org/keycloak/models/RealmModel.java
index 401eb6a..3b0b149 100755
--- a/model/api/src/main/java/org/keycloak/models/RealmModel.java
+++ b/model/api/src/main/java/org/keycloak/models/RealmModel.java
@@ -84,7 +84,7 @@ public interface RealmModel extends RoleContainerModel, RoleMapperModel, ScopeMa
UserModel addUser(String username);
- List<RoleModel> getDefaultRoles();
+ List<String> getDefaultRoles();
void addDefaultRole(String name);
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/ApplicationAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/ApplicationAdapter.java
index a93b669..00ed540 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/ApplicationAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/ApplicationAdapter.java
@@ -272,4 +272,61 @@ public class ApplicationAdapter implements ApplicationModel {
return query;
}
+ @Override
+ public List<String> getDefaultRoles() {
+ Collection<RoleEntity> entities = application.getDefaultRoles();
+ List<String> roles = new ArrayList<String>();
+ if (entities == null) return roles;
+ for (RoleEntity entity : entities) {
+ roles.add(entity.getName());
+ }
+ return roles;
+ }
+
+ @Override
+ public void addDefaultRole(String name) {
+ RoleModel role = getRole(name);
+ if (role == null) {
+ role = addRole(name);
+ }
+ Collection<RoleEntity> entities = application.getDefaultRoles();
+ for (RoleEntity entity : entities) {
+ if (entity.getId().equals(role.getId())) {
+ return;
+ }
+ }
+ entities.add(((RoleAdapter) role).getRole());
+ em.flush();
+ }
+
+ public static boolean contains(String str, String[] array) {
+ for (String s : array) {
+ if (str.equals(s)) return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void updateDefaultRoles(String[] defaultRoles) {
+ Collection<RoleEntity> entities = application.getDefaultRoles();
+ Set<String> already = new HashSet<String>();
+ List<RoleEntity> remove = new ArrayList<RoleEntity>();
+ for (RoleEntity rel : entities) {
+ if (!contains(rel.getName(), defaultRoles)) {
+ remove.add(rel);
+ } else {
+ already.add(rel.getName());
+ }
+ }
+ for (RoleEntity entity : remove) {
+ entities.remove(entity);
+ }
+ em.flush();
+ for (String roleName : defaultRoles) {
+ if (!already.contains(roleName)) {
+ addDefaultRole(roleName);
+ }
+ }
+ em.flush();
+ }
}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ApplicationEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ApplicationEntity.java
index a3ab9b7..54d628d 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ApplicationEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ApplicationEntity.java
@@ -33,6 +33,10 @@ public class ApplicationEntity {
@JoinTable(name="APPLICATION_ROLES")
Collection<RoleEntity> roles = new ArrayList<RoleEntity>();
+ @OneToMany(fetch = FetchType.LAZY, cascade ={CascadeType.REMOVE}, orphanRemoval = true)
+ @JoinTable(name="APPLICATION_DEFAULT_ROLES")
+ Collection<RoleEntity> defaultRoles = new ArrayList<RoleEntity>();
+
public String getId() {
return id;
}
@@ -84,4 +88,12 @@ public class ApplicationEntity {
public void setName(String name) {
this.name = name;
}
+
+ public Collection<RoleEntity> getDefaultRoles() {
+ return defaultRoles;
+ }
+
+ public void setDefaultRoles(Collection<RoleEntity> defaultRoles) {
+ this.defaultRoles = defaultRoles;
+ }
}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
index 658588c..8eb89d5 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
@@ -457,12 +457,12 @@ public class RealmAdapter implements RealmModel {
}
@Override
- public List<RoleModel> getDefaultRoles() {
+ public List<String> getDefaultRoles() {
Collection<RoleEntity> entities = realm.getDefaultRoles();
- List<RoleModel> roles = new ArrayList<RoleModel>();
+ List<String> roles = new ArrayList<String>();
if (entities == null) return roles;
for (RoleEntity entity : entities) {
- roles.add(new RoleAdapter(entity));
+ roles.add(entity.getName());
}
return roles;
}
@@ -504,8 +504,8 @@ public class RealmAdapter implements RealmModel {
}
for (RoleEntity entity : remove) {
entities.remove(entity);
- em.remove(entity);
}
+ em.flush();
for (String roleName : defaultRoles) {
if (!already.contains(roleName)) {
addDefaultRole(roleName);
@@ -543,6 +543,7 @@ public class RealmAdapter implements RealmModel {
em.persist(user);
applicationData.setApplicationUser(user);
applicationData.setName(name);
+ applicationData.setEnabled(true);
realm.getApplications().add(applicationData);
em.persist(applicationData);
em.flush();
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ApplicationAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ApplicationAdapter.java
index f145933..c1a4dd2 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ApplicationAdapter.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ApplicationAdapter.java
@@ -281,4 +281,19 @@ public class ApplicationAdapter implements ApplicationModel {
}
return result;
}
+
+ @Override
+ public List<String> getDefaultRoles() {
+ return null; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ @Override
+ public void addDefaultRole(String name) {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ @Override
+ public void updateDefaultRoles(String[] defaultRoles) {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
}
diff --git a/model/picketlink/src/main/java/org/keycloak/models/picketlink/ApplicationAdapter.java b/model/picketlink/src/main/java/org/keycloak/models/picketlink/ApplicationAdapter.java
index 8ad09eb..436b23c 100755
--- a/model/picketlink/src/main/java/org/keycloak/models/picketlink/ApplicationAdapter.java
+++ b/model/picketlink/src/main/java/org/keycloak/models/picketlink/ApplicationAdapter.java
@@ -15,10 +15,7 @@ import org.picketlink.idm.model.sample.SampleModel;
import org.picketlink.idm.query.IdentityQuery;
import org.picketlink.idm.query.RelationshipQuery;
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
+import java.util.*;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -257,4 +254,44 @@ public class ApplicationAdapter implements ApplicationModel {
return roles;
}
+ @Override
+ public List<String> getDefaultRoles() {
+ if ( applicationData.getDefaultRoles() != null) {
+ return Arrays.asList(applicationData.getDefaultRoles());
+ }
+ else {
+ return Collections.emptyList();
+ }
+ }
+
+ @Override
+ public void addDefaultRole(String name) {
+ if (getRole(name) == null) {
+ addRole(name);
+ }
+
+ String[] defaultRoles = applicationData.getDefaultRoles();
+ if (defaultRoles == null) {
+ defaultRoles = new String[1];
+ } else {
+ defaultRoles = Arrays.copyOf(defaultRoles, defaultRoles.length + 1);
+ }
+ defaultRoles[defaultRoles.length - 1] = name;
+
+ applicationData.setDefaultRoles(defaultRoles);
+ updateApplication();
+ }
+
+ @Override
+ public void updateDefaultRoles(String[] defaultRoles) {
+ for (String name : defaultRoles) {
+ if (getRole(name) == null) {
+ addRole(name);
+ }
+ }
+
+ applicationData.setDefaultRoles(defaultRoles);
+ updateApplication();
+ }
+
}
diff --git a/model/picketlink/src/main/java/org/keycloak/models/picketlink/mappings/ApplicationData.java b/model/picketlink/src/main/java/org/keycloak/models/picketlink/mappings/ApplicationData.java
index 8d5594a..5ce4aca 100755
--- a/model/picketlink/src/main/java/org/keycloak/models/picketlink/mappings/ApplicationData.java
+++ b/model/picketlink/src/main/java/org/keycloak/models/picketlink/mappings/ApplicationData.java
@@ -15,6 +15,7 @@ public class ApplicationData extends AbstractPartition {
private String managementUrl;
private String baseUrl;
private User resourceUser;
+ private String[] defaultRoles;
public ApplicationData() {
super(null);
@@ -76,4 +77,13 @@ public class ApplicationData extends AbstractPartition {
this.baseUrl = baseUrl;
}
+ @AttributeProperty
+ public String[] getDefaultRoles() {
+ return defaultRoles;
+ }
+
+ public void setDefaultRoles(String[] defaultRoles) {
+ this.defaultRoles = defaultRoles;
+ }
+
}
diff --git a/model/picketlink/src/main/java/org/keycloak/models/picketlink/mappings/ApplicationEntity.java b/model/picketlink/src/main/java/org/keycloak/models/picketlink/mappings/ApplicationEntity.java
index 0eddf7e..0e8d23b 100755
--- a/model/picketlink/src/main/java/org/keycloak/models/picketlink/mappings/ApplicationEntity.java
+++ b/model/picketlink/src/main/java/org/keycloak/models/picketlink/mappings/ApplicationEntity.java
@@ -34,6 +34,9 @@ public class ApplicationEntity implements Serializable {
@AttributeValue
private String baseUrl;
+ @AttributeValue
+ private String[] defaultRoles;
+
@OneToOne
@AttributeValue
AccountTypeEntity resourceUser;
@@ -94,4 +97,13 @@ public class ApplicationEntity implements Serializable {
public void setResourceUser(AccountTypeEntity resourceUser) {
this.resourceUser = resourceUser;
}
+
+ public String[] getDefaultRoles() {
+ return defaultRoles;
+ }
+
+ public void setDefaultRoles(String[] defaultRoles) {
+ this.defaultRoles = defaultRoles;
+ }
+
}
diff --git a/model/picketlink/src/main/java/org/keycloak/models/picketlink/RealmAdapter.java b/model/picketlink/src/main/java/org/keycloak/models/picketlink/RealmAdapter.java
index ed71165..3279abf 100755
--- a/model/picketlink/src/main/java/org/keycloak/models/picketlink/RealmAdapter.java
+++ b/model/picketlink/src/main/java/org/keycloak/models/picketlink/RealmAdapter.java
@@ -43,13 +43,7 @@ import java.io.StringWriter;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.X509Certificate;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
+import java.util.*;
/**
* Meant to be a per-request object
@@ -767,17 +761,15 @@ public class RealmAdapter implements RealmModel {
}
@Override
- public List<RoleModel> getDefaultRoles() {
- List<RoleModel> defaultRoleModels = new ArrayList<RoleModel>();
- if (realm.getDefaultRoles() != null) {
- for (String name : realm.getDefaultRoles()) {
- RoleAdapter role = getRole(name);
- if (role != null) {
- defaultRoleModels.add(role);
- }
- }
+ public List<String> getDefaultRoles() {
+ if (realm.getDefaultRoles() == null) return Collections.emptyList();
+ List<String> list = new ArrayList<String>();
+ for (String role : realm.getDefaultRoles()) {
+ RoleModel model = getRole(role);
+ if (model == null) throw new RuntimeException("default role missing");
+ list.add(role);
}
- return defaultRoleModels;
+ return list;
}
@Override
model/pom.xml 2(+1 -1)
diff --git a/model/pom.xml b/model/pom.xml
index 3d0b0dc..26992ca 100755
--- a/model/pom.xml
+++ b/model/pom.xml
@@ -37,6 +37,6 @@
<module>api</module>
<module>picketlink</module>
<module>jpa</module>
- <module>mongo</module>
+ <!-- <module>mongo</module> -->
</modules>
</project>
pom.xml 4(+3 -1)
diff --git a/pom.xml b/pom.xml
index 3b446c8..eda9912 100755
--- a/pom.xml
+++ b/pom.xml
@@ -20,6 +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>
</properties>
<url>http://keycloak.org</url>
@@ -72,7 +73,8 @@
<module>admin-ui</module>
<module>examples</module>
<module>testsuite</module>
- <!--<module>ui</module> -->
+ <module>server</module>
+ <module>dist</module>
</modules>
<dependencyManagement>
server/pom.xml 149(+149 -0)
diff --git a/server/pom.xml b/server/pom.xml
new file mode 100755
index 0000000..51b450c
--- /dev/null
+++ b/server/pom.xml
@@ -0,0 +1,149 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <parent>
+ <artifactId>keycloak-parent</artifactId>
+ <groupId>org.keycloak</groupId>
+ <version>1.0-alpha-1</version>
+ </parent>
+
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-server</artifactId>
+ <packaging>war</packaging>
+ <name>Keycloak Server</name>
+ <description/>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.jboss.resteasy</groupId>
+ <artifactId>jose-jwt</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-admin-ui</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-admin-ui-styles</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-core</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-services</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <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>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-social-core</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-social-google</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-social-twitter</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-social-facebook</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <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>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <finalName>auth-server</finalName>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <configuration>
+ <source>1.6</source>
+ <target>1.6</target>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/server/src/main/java/org/keycloak/server/KeycloakServerApplication.java b/server/src/main/java/org/keycloak/server/KeycloakServerApplication.java
new file mode 100755
index 0000000..cb91e73
--- /dev/null
+++ b/server/src/main/java/org/keycloak/server/KeycloakServerApplication.java
@@ -0,0 +1,28 @@
+package org.keycloak.server;
+
+import org.jboss.resteasy.jwt.JsonSerialization;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.services.managers.ApplianceBootstrap;
+import org.keycloak.services.managers.RealmManager;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.services.resources.KeycloakApplication;
+
+import javax.servlet.ServletContext;
+import javax.ws.rs.core.Context;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+public class KeycloakServerApplication extends KeycloakApplication {
+
+ public KeycloakServerApplication(@Context ServletContext servletContext) {
+ super(servletContext);
+ KeycloakSession session = factory.createSession();
+ session.getTransaction().begin();
+ ApplianceBootstrap bootstrap = new ApplianceBootstrap();
+ bootstrap.bootstrap(session);
+ session.getTransaction().commit();
+ }
+
+}
diff --git a/server/src/main/resources/META-INF/persistence.xml b/server/src/main/resources/META-INF/persistence.xml
new file mode 100755
index 0000000..5d5eed0
--- /dev/null
+++ b/server/src/main/resources/META-INF/persistence.xml
@@ -0,0 +1,33 @@
+<persistence xmlns="http://java.sun.com/xml/ns/persistence"
+ 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>
+
+ <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" />
+ </properties>
+ </persistence-unit>
+
+</persistence>
diff --git a/server/src/main/webapp/WEB-INF/jboss-deployment-structure.xml b/server/src/main/webapp/WEB-INF/jboss-deployment-structure.xml
new file mode 100755
index 0000000..8caa96f
--- /dev/null
+++ b/server/src/main/webapp/WEB-INF/jboss-deployment-structure.xml
@@ -0,0 +1,10 @@
+<jboss-deployment-structure>
+ <deployment>
+ <!-- This allows you to define additional dependencies, it is the same as using the Dependencies: manifest attribute -->
+ <dependencies>
+ <module name="org.jboss.resteasy.jose-jwt"/>
+ <module name="org.jboss.resteasy.resteasy-crypto"/>
+ <module name="org.bouncycastle"/>
+ </dependencies>
+ </deployment>
+</jboss-deployment-structure>
\ No newline at end of file
server/src/main/webapp/WEB-INF/web.xml 39(+39 -0)
diff --git a/server/src/main/webapp/WEB-INF/web.xml b/server/src/main/webapp/WEB-INF/web.xml
new file mode 100755
index 0000000..08bf314
--- /dev/null
+++ b/server/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<web-app xmlns="http://java.sun.com/xml/ns/javaee"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
+ version="3.0">
+
+ <module-name>auth-server</module-name>
+
+ <servlet>
+ <servlet-name>Resteasy</servlet-name>
+ <servlet-class>org.jboss.resteasy.plugins.server.servlet.HttpServlet30Dispatcher</servlet-class>
+ <init-param>
+ <param-name>javax.ws.rs.Application</param-name>
+ <param-value>org.keycloak.server.KeycloakServerApplication</param-value>
+ </init-param>
+ <init-param>
+ <param-name>resteasy.servlet.mapping.prefix</param-name>
+ <param-value>/rest</param-value>
+ </init-param>
+ <load-on-startup>1</load-on-startup>
+ <async-supported>true</async-supported>
+ </servlet>
+
+ <filter>
+ <filter-name>Keycloak Session Management</filter-name>
+ <filter-class>org.keycloak.services.filters.KeycloakSessionServletFilter</filter-class>
+ </filter>
+
+ <filter-mapping>
+ <filter-name>Keycloak Session Management</filter-name>
+ <url-pattern>/rest/*</url-pattern>
+ </filter-mapping>
+
+ <servlet-mapping>
+ <servlet-name>Resteasy</servlet-name>
+ <url-pattern>/rest/*</url-pattern>
+ </servlet-mapping>
+
+</web-app>
diff --git a/services/src/main/java/org/keycloak/services/email/EmailSender.java b/services/src/main/java/org/keycloak/services/email/EmailSender.java
index 6bd1ba2..185ca83 100755
--- a/services/src/main/java/org/keycloak/services/email/EmailSender.java
+++ b/services/src/main/java/org/keycloak/services/email/EmailSender.java
@@ -48,26 +48,53 @@ public class EmailSender {
private static final Logger log = Logger.getLogger(EmailSender.class);
- private Properties properties;
+ private Map<String, String> config;
public EmailSender(Map<String, String> config) {
- properties = new Properties();
- for (Entry<String, String> e : config.entrySet()) {
- properties.put("mail.smtp." + e.getKey(), e.getValue());
- }
+ this.config = config;
}
public void send(String address, String subject, String body) throws MessagingException {
- Session session = Session.getInstance(properties);
+ Properties props = new Properties();
+ props.setProperty("mail.smtp.host", config.get("host"));
+
+ boolean auth = "true".equals(config.get("auth"));
+ boolean ssl = "true".equals(config.get("ssl"));
+ boolean starttls = "true".equals(config.get("starttls"));
+
+ if (config.containsKey("port")) {
+ props.setProperty("mail.smtp.port", config.get("port"));
+ }
+
+ if (auth) {
+ props.put("mail.smtp.auth", "true");
+ }
+
+ if (ssl) {
+ props.put("mail.smtp.socketFactory.port", config.get("port"));
+ props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
+ }
+
+ if (starttls) {
+ props.put("mail.smtp.starttls.enable", "true");
+ }
+
+ String from = config.get("from");
+
+ Session session = Session.getInstance(props);
Message msg = new MimeMessage(session);
- msg.setFrom(new InternetAddress(properties.getProperty("mail.smtp.from")));
+ msg.setFrom(new InternetAddress(from));
msg.setSubject(subject);
msg.setText(body);
msg.saveChanges();
Transport transport = session.getTransport("smtp");
- transport.connect(properties.getProperty("mail.smtp.user"), properties.getProperty("mail.smtp.password"));
+ if (auth) {
+ transport.connect(config.get("user"), config.get("password"));
+ } else {
+ transport.connect();
+ }
transport.sendMessage(msg, new InternetAddress[] { new InternetAddress(address) });
}
diff --git a/services/src/main/java/org/keycloak/services/managers/ApplicationManager.java b/services/src/main/java/org/keycloak/services/managers/ApplicationManager.java
index a2464e8..67a8286 100755
--- a/services/src/main/java/org/keycloak/services/managers/ApplicationManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/ApplicationManager.java
@@ -70,6 +70,11 @@ public class ApplicationManager {
if (roleRep.getDescription() != null) role.setDescription(roleRep.getDescription());
}
}
+
+ if (resourceRep.getDefaultRoles() != null) {
+ applicationModel.updateDefaultRoles(resourceRep.getDefaultRoles());
+ }
+
if (resourceRep.getRoleMappings() != null) {
for (UserRoleMappingRepresentation mapping : resourceRep.getRoleMappings()) {
UserModel user = realm.getUser(mapping.getUsername());
@@ -114,6 +119,10 @@ public class ApplicationManager {
resource.setSurrogateAuthRequired(rep.isSurrogateAuthRequired());
resource.updateApplication();
+ if (rep.getDefaultRoles() != null) {
+ resource.updateDefaultRoles(rep.getDefaultRoles());
+ }
+
List<String> redirectUris = rep.getRedirectUris();
if (redirectUris != null) {
resource.getApplicationUser().setRedirectUris(new HashSet<String>(redirectUris));
@@ -144,6 +153,10 @@ public class ApplicationManager {
rep.setWebOrigins(new LinkedList<String>(webOrigins));
}
+ if (!applicationModel.getDefaultRoles().isEmpty()) {
+ rep.setDefaultRoles(applicationModel.getDefaultRoles().toArray(new String[0]));
+ }
+
return rep;
}
diff --git a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
index 8206eac..a3434b3 100755
--- a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
@@ -56,24 +56,27 @@ public class AuthenticationManager {
String cookieName = KEYCLOAK_IDENTITY_COOKIE;
URI uri = RealmsResource.realmBaseUrl(uriInfo).build(realm.getId());
String cookiePath = uri.getPath();
- return createLoginCookie(realm, user, cookieName, cookiePath);
+ return createLoginCookie(realm, user, null, cookieName, cookiePath);
}
public NewCookie createSaasIdentityCookie(RealmModel realm, UserModel user, UriInfo uriInfo) {
String cookieName = SaasService.SAAS_IDENTITY_COOKIE;
URI uri = SaasService.saasCookiePath(uriInfo).build();
String cookiePath = uri.getPath();
- return createLoginCookie(realm, user, cookieName, cookiePath);
+ return createLoginCookie(realm, user, null, cookieName, cookiePath);
}
- public NewCookie createAccountIdentityCookie(RealmModel realm, UserModel user, URI uri) {
+ public NewCookie createAccountIdentityCookie(RealmModel realm, UserModel user, UserModel client, URI uri) {
String cookieName = AccountService.ACCOUNT_IDENTITY_COOKIE;
String cookiePath = uri.getPath();
- return createLoginCookie(realm, user, cookieName, cookiePath);
+ return createLoginCookie(realm, user, client, cookieName, cookiePath);
}
- protected NewCookie createLoginCookie(RealmModel realm, UserModel user, String cookieName, String cookiePath) {
+ protected NewCookie createLoginCookie(RealmModel realm, UserModel user, UserModel client, String cookieName, String cookiePath) {
SkeletonKeyToken identityToken = createIdentityToken(realm, user.getLoginName());
+ if (client != null) {
+ identityToken.issuedFor(client.getLoginName());
+ }
String encoded = encodeToken(realm, identityToken);
boolean secureOnly = !realm.isSslNotRequired();
logger.debug("creatingLoginCookie - name: {0} path: {1}", cookieName, cookiePath);
@@ -127,15 +130,17 @@ public class AuthenticationManager {
public UserModel authenticateIdentityCookie(RealmModel realm, UriInfo uriInfo, HttpHeaders headers) {
String cookieName = KEYCLOAK_IDENTITY_COOKIE;
- return authenticateIdentityCookie(realm, uriInfo, headers, cookieName);
+ Auth auth = authenticateIdentityCookie(realm, uriInfo, headers, cookieName);
+ return auth != null ? auth.getUser() : null;
}
public UserModel authenticateSaasIdentityCookie(RealmModel realm, UriInfo uriInfo, HttpHeaders headers) {
String cookieName = SaasService.SAAS_IDENTITY_COOKIE;
- return authenticateIdentityCookie(realm, uriInfo, headers, cookieName);
+ Auth auth = authenticateIdentityCookie(realm, uriInfo, headers, cookieName);
+ return auth != null ? auth.getUser() : null;
}
- public UserModel authenticateAccountIdentityCookie(RealmModel realm, UriInfo uriInfo, HttpHeaders headers) {
+ public Auth authenticateAccountIdentityCookie(RealmModel realm, UriInfo uriInfo, HttpHeaders headers) {
String cookieName = AccountService.ACCOUNT_IDENTITY_COOKIE;
return authenticateIdentityCookie(realm, uriInfo, headers, cookieName);
}
@@ -144,11 +149,19 @@ public class AuthenticationManager {
UserModel user = authenticateSaasIdentityCookie(realm, uriInfo, headers);
if (user != null) return user;
+ Auth auth = authenticateBearerToken(realm, headers);
+ return auth != null ? auth.getUser() : null;
+ }
+
+ public Auth authenticateAccountIdentity(RealmModel realm, UriInfo uriInfo, HttpHeaders headers) {
+ Auth auth = authenticateAccountIdentityCookie(realm, uriInfo, headers);
+ if (auth != null) return auth;
+
return authenticateBearerToken(realm, headers);
}
- protected UserModel authenticateIdentityCookie(RealmModel realm, UriInfo uriInfo, HttpHeaders headers, String cookieName) {
+ protected Auth authenticateIdentityCookie(RealmModel realm, UriInfo uriInfo, HttpHeaders headers, String cookieName) {
Cookie cookie = headers.getCookies().get(cookieName);
if (cookie == null) {
logger.debug("authenticateCookie could not find cookie: {0}", cookieName);
@@ -163,13 +176,28 @@ public class AuthenticationManager {
expireIdentityCookie(realm, uriInfo);
return null;
}
+
+ Auth auth = new Auth(token);
+
UserModel user = realm.getUser(token.getPrincipal());
if (user == null || !user.isEnabled()) {
logger.debug("Unknown user in identity cookie");
expireIdentityCookie(realm, uriInfo);
return null;
}
- return user;
+ auth.setUser(user);
+
+ if (token.getIssuedFor() != null) {
+ UserModel client = realm.getUser(token.getIssuedFor());
+ if (client == null || !client.isEnabled()) {
+ logger.debug("Unknown client in identity cookie");
+ expireIdentityCookie(realm, uriInfo);
+ return null;
+ }
+ auth.setClient(client);
+ }
+
+ return auth;
} catch (VerificationException e) {
logger.debug("Failed to verify identity cookie", e);
expireIdentityCookie(realm, uriInfo);
@@ -177,11 +205,11 @@ public class AuthenticationManager {
return null;
}
- public UserModel authenticateBearerToken(RealmModel realm, HttpHeaders headers) {
+ public Auth authenticateBearerToken(RealmModel realm, HttpHeaders headers) {
String tokenString = null;
String authHeader = headers.getHeaderString(HttpHeaders.AUTHORIZATION);
if (authHeader == null) {
- throw new NotAuthorizedException("Bearer");
+ return null;
} else {
String[] split = authHeader.trim().split("\\s+");
if (split == null || split.length != 2) throw new NotAuthorizedException("Bearer");
@@ -195,11 +223,24 @@ public class AuthenticationManager {
if (!token.isActive()) {
throw new NotAuthorizedException("token_expired");
}
+
+ Auth auth = new Auth(token);
+
UserModel user = realm.getUser(token.getPrincipal());
if (user == null || !user.isEnabled()) {
throw new NotAuthorizedException("invalid_user");
}
- return user;
+ auth.setUser(user);
+
+ if (token.getIssuedFor() != null) {
+ UserModel client = realm.getUser(token.getIssuedFor());
+ if (client == null || !client.isEnabled()) {
+ throw new NotAuthorizedException("invalid_user");
+ }
+ auth.setClient(client);
+ }
+
+ return auth;
} catch (VerificationException e) {
logger.error("Failed to verify token", e);
throw new NotAuthorizedException("invalid_token");
@@ -271,4 +312,34 @@ public class AuthenticationManager {
SUCCESS, ACCOUNT_DISABLED, ACTIONS_REQUIRED, INVALID_USER, INVALID_CREDENTIALS, MISSING_PASSWORD, MISSING_TOTP, FAILED
}
+ public static class Auth {
+ private SkeletonKeyToken token;
+ private UserModel user;
+ private UserModel client;
+
+ public Auth(SkeletonKeyToken token) {
+ this.token = token;
+ }
+
+ public SkeletonKeyToken getToken() {
+ return token;
+ }
+
+ public UserModel getUser() {
+ return user;
+ }
+
+ public UserModel getClient() {
+ return client;
+ }
+
+ void setUser(UserModel user) {
+ this.user = user;
+ }
+
+ void setClient(UserModel client) {
+ this.client = client;
+ }
+ }
+
}
diff --git a/services/src/main/java/org/keycloak/services/managers/RealmManager.java b/services/src/main/java/org/keycloak/services/managers/RealmManager.java
index d131e84..8cd43a0 100755
--- a/services/src/main/java/org/keycloak/services/managers/RealmManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/RealmManager.java
@@ -110,7 +110,7 @@ public class RealmManager {
realm.updateRequiredApplicationCredentials(rep.getRequiredApplicationCredentials());
}
if (rep.getDefaultRoles() != null) {
- realm.updateDefaultRoles(rep.getDefaultRoles());
+ realm.updateDefaultRoles(rep.getDefaultRoles().toArray(new String[rep.getDefaultRoles().size()]));
}
if (rep.getAccountManagement() != null && rep.getAccountManagement()) {
@@ -129,9 +129,11 @@ public class RealmManager {
}
private void enableAccountManagement(RealmModel realm) {
- ApplicationModel application = realm.getApplicationById(Constants.ACCOUNT_MANAGEMENT_APPLICATION);
+ ApplicationModel application = realm.getApplicationById(Constants.ACCOUNT_APPLICATION);
if (application == null) {
- application = realm.addApplication(Constants.ACCOUNT_MANAGEMENT_APPLICATION);
+ application = realm.addApplication(Constants.ACCOUNT_APPLICATION);
+ application.addDefaultRole(Constants.ACCOUNT_PROFILE_ROLE);
+ application.addDefaultRole(Constants.ACCOUNT_MANAGE_ROLE);
UserCredentialModel password = new UserCredentialModel();
password.setType(UserCredentialModel.PASSWORD);
@@ -146,7 +148,7 @@ public class RealmManager {
}
private void disableAccountManagement(RealmModel realm) {
- ApplicationModel application = realm.getApplicationNameMap().get(Constants.ACCOUNT_MANAGEMENT_APPLICATION);
+ ApplicationModel application = realm.getApplicationNameMap().get(Constants.ACCOUNT_APPLICATION);
if (application != null) {
application.setEnabled(false); // TODO Should we delete the application instead?
}
@@ -410,9 +412,11 @@ public class RealmManager {
rep.setLastName(user.getLastName());
rep.setFirstName(user.getFirstName());
rep.setEmail(user.getEmail());
- Map<String, String> attrs = new HashMap<String, String>();
- attrs.putAll(user.getAttributes());
- rep.setAttributes(attrs);
+ if (user.getAttributes() != null && !user.getAttributes().isEmpty()) {
+ Map<String, String> attrs = new HashMap<String, String>();
+ attrs.putAll(user.getAttributes());
+ rep.setAttributes(attrs);
+ }
return rep;
}
@@ -444,16 +448,14 @@ public class RealmManager {
rep.setSmtpServer(realm.getSmtpConfig());
rep.setSocialProviders(realm.getSocialConfig());
- ApplicationModel accountManagementApplication = realm.getApplicationNameMap().get(Constants.ACCOUNT_MANAGEMENT_APPLICATION);
+ ApplicationModel accountManagementApplication = realm.getApplicationNameMap().get(Constants.ACCOUNT_APPLICATION);
rep.setAccountManagement(accountManagementApplication != null && accountManagementApplication.isEnabled());
- List<RoleModel> defaultRoles = realm.getDefaultRoles();
- if (defaultRoles.size() > 0) {
- String[] d = new String[defaultRoles.size()];
- for (int i = 0; i < d.length; i++) {
- d[i] = defaultRoles.get(i).getName();
- }
- rep.setDefaultRoles(d);
+ List<String> defaultRoles = realm.getDefaultRoles();
+ if (!defaultRoles.isEmpty()) {
+ List<String> roleStrings = new ArrayList<String>();
+ roleStrings.addAll(defaultRoles);
+ rep.setDefaultRoles(roleStrings);
}
List<RequiredCredentialModel> requiredCredentialModels = realm.getRequiredCredentials();
diff --git a/services/src/main/java/org/keycloak/services/managers/TokenManager.java b/services/src/main/java/org/keycloak/services/managers/TokenManager.java
index f053173..d5b955f 100755
--- a/services/src/main/java/org/keycloak/services/managers/TokenManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/TokenManager.java
@@ -15,6 +15,7 @@ import org.keycloak.representations.SkeletonKeyToken;
import javax.ws.rs.core.MultivaluedMap;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -51,12 +52,16 @@ public class TokenManager {
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;
}
@@ -71,11 +76,15 @@ public class TokenManager {
}
for (ApplicationModel resource : realm.getApplications()) {
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;
}
diff --git a/services/src/main/java/org/keycloak/services/resources/AccountService.java b/services/src/main/java/org/keycloak/services/resources/AccountService.java
index eab8766..f176061 100755
--- a/services/src/main/java/org/keycloak/services/resources/AccountService.java
+++ b/services/src/main/java/org/keycloak/services/resources/AccountService.java
@@ -27,15 +27,13 @@ import org.jboss.resteasy.logging.Logger;
import org.jboss.resteasy.spi.HttpRequest;
import org.keycloak.AbstractOAuthClient;
import org.keycloak.jaxrs.JaxrsOAuthClient;
-import org.keycloak.models.ApplicationModel;
-import org.keycloak.models.Constants;
-import org.keycloak.models.RealmModel;
-import org.keycloak.models.UserCredentialModel;
-import org.keycloak.models.UserModel;
+import org.keycloak.models.*;
import org.keycloak.models.utils.TimeBasedOTP;
+import org.keycloak.representations.SkeletonKeyToken;
import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.services.managers.AccessCodeEntry;
import org.keycloak.services.managers.AuthenticationManager;
+import org.keycloak.services.managers.RealmManager;
import org.keycloak.services.managers.TokenManager;
import org.keycloak.services.messages.Messages;
import org.keycloak.services.resources.flows.Flows;
@@ -44,23 +42,11 @@ import org.keycloak.services.resources.flows.Pages;
import org.keycloak.services.resources.flows.Urls;
import org.keycloak.services.validation.Validation;
-import javax.ws.rs.BadRequestException;
-import javax.ws.rs.Consumes;
-import javax.ws.rs.ForbiddenException;
-import javax.ws.rs.GET;
-import javax.ws.rs.POST;
-import javax.ws.rs.Path;
-import javax.ws.rs.QueryParam;
-import javax.ws.rs.core.Context;
-import javax.ws.rs.core.HttpHeaders;
-import javax.ws.rs.core.MediaType;
-import javax.ws.rs.core.MultivaluedMap;
-import javax.ws.rs.core.NewCookie;
-import javax.ws.rs.core.Response;
-import javax.ws.rs.core.UriBuilder;
-import javax.ws.rs.core.UriInfo;
+import javax.ws.rs.*;
+import javax.ws.rs.core.*;
import javax.ws.rs.ext.Providers;
import java.net.URI;
+import java.util.List;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@@ -98,18 +84,42 @@ public class AccountService {
}
private Response forwardToPage(String path, String template) {
- UserModel user = getUser(false);
- if (user != null) {
- return Flows.forms(realm, request, uriInfo).setUser(user).forwardToForm(template);
+ AuthenticationManager.Auth auth = getAuth(false);
+ if (auth != null) {
+ if (!hasAccess(auth)) {
+ return noAccess();
+ }
+ return Flows.forms(realm, request, uriInfo).setUser(auth.getUser()).forwardToForm(template);
} else {
return login(path);
}
}
- @Path("")
+ private Response noAccess() {
+ return Flows.forms(realm, request, uriInfo).setError("No access").forwardToErrorPage();
+ }
+
+ @Path("/")
+ @OPTIONS
+ public Response accountPreflight() {
+ return Cors.add(request, Response.ok()).auth().preflight().build();
+ }
+
+ @Path("/")
@GET
public Response accountPage() {
- return forwardToPage(null, Pages.ACCOUNT);
+ List<MediaType> types = headers.getAcceptableMediaTypes();
+ if (types.contains(MediaType.WILDCARD_TYPE) || (types.contains(MediaType.TEXT_HTML_TYPE))) {
+ return forwardToPage(null, Pages.ACCOUNT);
+ } else if (types.contains(MediaType.APPLICATION_JSON_TYPE)) {
+ AuthenticationManager.Auth auth = getAuth(true);
+ if (!hasAccess(auth, Constants.ACCOUNT_PROFILE_ROLE)) {
+ return Response.status(Response.Status.FORBIDDEN).build();
+ }
+ return Cors.add(request, Response.ok(RealmManager.toRepresentation(auth.getUser()))).auth().allowedOrigins(auth.getClient()).build();
+ } else {
+ return Response.notAcceptable(Variant.VariantListBuilder.newInstance().mediaTypes(MediaType.TEXT_HTML_TYPE, MediaType.APPLICATION_JSON_TYPE).build()).build();
+ }
}
@Path("social")
@@ -136,12 +146,16 @@ public class AccountService {
return forwardToPage("access", Pages.ACCESS);
}
- @Path("")
+ @Path("/")
@POST
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public Response processAccountUpdate(final MultivaluedMap<String, String> formData) {
+ AuthenticationManager.Auth auth = getAuth(true);
+ if (!hasAccess(auth)) {
+ return noAccess();
+ }
- UserModel user = getUser(true);
+ UserModel user = auth.getUser();
String error = Validation.validateUpdateProfileForm(formData);
if (error != null) {
@@ -159,7 +173,13 @@ public class AccountService {
@Path("totp-remove")
@GET
public Response processTotpRemove() {
- UserModel user = getUser(true);
+ AuthenticationManager.Auth auth = getAuth(true);
+ if (!hasAccess(auth)) {
+ return noAccess();
+ }
+
+ UserModel user = auth.getUser();
+
user.setTotp(false);
return Flows.forms(realm, request, uriInfo).setError("successTotpRemoved").setErrorType(FormFlows.MessageType.SUCCESS)
.setUser(user).forwardToTotp();
@@ -169,7 +189,12 @@ public class AccountService {
@POST
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public Response processTotpUpdate(final MultivaluedMap<String, String> formData) {
- UserModel user = getUser(true);
+ AuthenticationManager.Auth auth = getAuth(true);
+ if (!hasAccess(auth)) {
+ return noAccess();
+ }
+
+ UserModel user = auth.getUser();
String totp = formData.getFirst("totp");
String totpSecret = formData.getFirst("totpSecret");
@@ -196,7 +221,12 @@ public class AccountService {
@POST
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public Response processPasswordUpdate(final MultivaluedMap<String, String> formData) {
- UserModel user = getUser(true);
+ AuthenticationManager.Auth auth = getAuth(true);
+ if (!hasAccess(auth)) {
+ return noAccess();
+ }
+
+ UserModel user = auth.getUser();
FormFlows forms = Flows.forms(realm, request, uriInfo).setUser(user);
@@ -297,7 +327,7 @@ public class AccountService {
}
URI redirectUri = redirectBuilder.build(realm.getId());
- NewCookie cookie = authManager.createAccountIdentityCookie(realm, accessCode.getUser(), Urls.accountBase(uriInfo.getBaseUri()).build(realm.getId()));
+ NewCookie cookie = authManager.createAccountIdentityCookie(realm, accessCode.getUser(), client, Urls.accountBase(uriInfo.getBaseUri()).build(realm.getId()));
return Response.status(302).cookie(cookie).location(redirectUri).build();
} finally {
authManager.expireCookie(AbstractOAuthClient.OAUTH_TOKEN_REQUEST_STATE, uriInfo.getAbsolutePath().getPath());
@@ -319,7 +349,7 @@ public class AccountService {
String authUrl = Urls.realmLoginPage(uriInfo.getBaseUri(), realm.getId()).toString();
oauth.setAuthUrl(authUrl);
- oauth.setClientId(Constants.ACCOUNT_MANAGEMENT_APPLICATION);
+ oauth.setClientId(Constants.ACCOUNT_APPLICATION);
URI accountUri = Urls.accountPageBuilder(uriInfo.getBaseUri()).path(AccountService.class, "loginRedirect").build(realm.getId());
@@ -327,11 +357,42 @@ public class AccountService {
return oauth.redirect(uriInfo, accountUri.toString(), path);
}
- private UserModel getUser(boolean required) {
- UserModel user = authManager.authenticateAccountIdentityCookie(realm, uriInfo, headers);
- if (user == null && required) {
+ private AuthenticationManager.Auth getAuth(boolean error) {
+ AuthenticationManager.Auth auth = authManager.authenticateAccountIdentity(realm, uriInfo, headers);
+ if (auth == null && error) {
throw new ForbiddenException();
}
- return user;
+ return auth;
+ }
+
+ private boolean hasAccess(AuthenticationManager.Auth auth) {
+ return hasAccess(auth, null);
}
+
+ private boolean hasAccess(AuthenticationManager.Auth auth, String role) {
+ 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))) {
+ return true;
+ }
+ }
+
+ SkeletonKeyToken.Access access = auth.getToken().getResourceAccess(application.getName());
+ if (access != null) {
+ if (access.isUserInRole(Constants.ACCOUNT_MANAGE_ROLE) || (role != null && access.isUserInRole(role))) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private boolean hasRole(UserModel user, String role) {
+ if (application.getDefaultRoles().contains(role)) {
+ return true;
+ }
+ return application.hasRole(user, role);
+ }
+
}
diff --git a/services/src/main/java/org/keycloak/services/resources/Cors.java b/services/src/main/java/org/keycloak/services/resources/Cors.java
index 53c5eaa..42c34d1 100755
--- a/services/src/main/java/org/keycloak/services/resources/Cors.java
+++ b/services/src/main/java/org/keycloak/services/resources/Cors.java
@@ -1,19 +1,37 @@
package org.keycloak.services.resources;
-import org.jboss.resteasy.spi.HttpRequest;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.ResponseBuilder;
-import java.util.Set;
+
+import org.jboss.resteasy.spi.HttpRequest;
+import org.keycloak.models.UserModel;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class Cors {
+ public static final long DEFAULT_MAX_AGE = TimeUnit.HOURS.toSeconds(1);
+ public static final String DEFAULT_ALLOW_METHODS = "GET, OPTIONS";
+
+ public static final String ORIGIN = "Origin";
+
+ public static final String ACCESS_CONTROL_ALLOW_ORIGIN = "Access-Control-Allow-Origin";
+ public static final String ACCESS_CONTROL_ALLOW_METHODS = "Access-Control-Allow-Methods";
+ public static final String ACCESS_CONTROL_ALLOW_HEADERS = "Access-Control-Allow-Headers";
+ public static final String ACCESS_CONTROL_ALLOW_CREDENTIALS = "Access-Control-Allow-Credentials";
+ public static final String ACCESS_CONTROL_MAX_AGE = "Access-Control-Max-Age";
+
private HttpRequest request;
private ResponseBuilder response;
private Set<String> allowedOrigins;
+ private String[] allowedMethods;
+
+ private boolean preflight;
+ private boolean auth;
public Cors(HttpRequest request, ResponseBuilder response) {
this.request = request;
@@ -24,18 +42,60 @@ public class Cors {
return new Cors(request, response);
}
- public Cors allowedOrigins(Set<String> allowedOrigins) {
- this.allowedOrigins = allowedOrigins;
+ public Cors preflight() {
+ preflight = true;
+ return this;
+ }
+
+ public Cors auth() {
+ auth = true;
+ return this;
+ }
+
+ public Cors allowedOrigins(UserModel client) {
+ if (client != null) {
+ allowedOrigins = client.getWebOrigins();
+ }
+ return this;
+ }
+
+ public Cors allowedMethods(String... allowedMethods) {
+ this.allowedMethods = allowedMethods;
return this;
}
public Response build() {
- String origin = request.getHttpHeaders().getHeaderString("Origin");
- if (origin == null || allowedOrigins == null || (!allowedOrigins.contains(origin))) {
+ String origin = request.getHttpHeaders().getHeaderString(ORIGIN);
+ if (origin == null) {
+ return response.build();
+ }
+
+ if (!preflight && (allowedOrigins == null || !allowedOrigins.contains(origin))) {
return response.build();
}
- response.header("Access-Control-Allow-Origin", origin);
+ response.header(ACCESS_CONTROL_ALLOW_ORIGIN, origin);
+
+ if (allowedMethods != null) {
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < allowedMethods.length; i++) {
+ if (i > 0) {
+ sb.append(", ");
+ }
+ sb.append(allowedMethods[i]);
+ }
+ response.header(ACCESS_CONTROL_ALLOW_METHODS, sb.toString());
+ } else {
+ response.header(ACCESS_CONTROL_ALLOW_METHODS, DEFAULT_ALLOW_METHODS);
+ }
+
+ response.header(ACCESS_CONTROL_ALLOW_CREDENTIALS, Boolean.toString(auth));
+ if (auth) {
+ response.header(ACCESS_CONTROL_ALLOW_HEADERS, "Authorization");
+ }
+
+ response.header(ACCESS_CONTROL_MAX_AGE, DEFAULT_MAX_AGE);
+
return response.build();
}
diff --git a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java
index 07d1cb6..867c2c1 100755
--- a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java
+++ b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java
@@ -3,9 +3,9 @@ package org.keycloak.services.resources;
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 org.keycloak.social.SocialRequestManager;
import javax.annotation.PreDestroy;
import javax.servlet.ServletContext;
diff --git a/services/src/main/java/org/keycloak/services/resources/RealmsResource.java b/services/src/main/java/org/keycloak/services/resources/RealmsResource.java
index 1247995..f96f343 100755
--- a/services/src/main/java/org/keycloak/services/resources/RealmsResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/RealmsResource.java
@@ -69,7 +69,7 @@ public class RealmsResource {
throw new NotFoundException();
}
- ApplicationModel application = realm.getApplicationNameMap().get(Constants.ACCOUNT_MANAGEMENT_APPLICATION);
+ ApplicationModel application = realm.getApplicationNameMap().get(Constants.ACCOUNT_APPLICATION);
if (application == null || !application.isEnabled()) {
logger.debug("account management not enabled");
throw new NotFoundException();
diff --git a/services/src/main/java/org/keycloak/services/resources/SocialResource.java b/services/src/main/java/org/keycloak/services/resources/SocialResource.java
index 1db79ea..908844c 100755
--- a/services/src/main/java/org/keycloak/services/resources/SocialResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/SocialResource.java
@@ -39,12 +39,11 @@ import org.keycloak.services.resources.flows.Urls;
import org.keycloak.social.AuthCallback;
import org.keycloak.social.AuthRequest;
import org.keycloak.social.RequestDetails;
-import org.keycloak.social.RequestDetailsBuilder;
import org.keycloak.social.SocialConstants;
import org.keycloak.social.SocialProvider;
import org.keycloak.social.SocialProviderConfig;
import org.keycloak.social.SocialProviderException;
-import org.keycloak.social.SocialRequestManager;
+import org.keycloak.services.managers.SocialRequestManager;
import org.keycloak.social.SocialUser;
import javax.imageio.spi.ServiceRegistry;
@@ -181,19 +180,12 @@ public class SocialResource {
}
realm.addSocialLink(user, socialLink);
-
- for (RoleModel role : realm.getDefaultRoles()) {
- realm.grantRole(user, role);
- }
} else {
// Redirect user to registration screen with prefilled data from social provider
MultivaluedMap<String, String> formData = fillRegistrationFormWithSocialData(socialUser);
- RequestDetailsBuilder reqDetailsBuilder = RequestDetailsBuilder.createFromRequestDetails(requestData);
- reqDetailsBuilder.putSocialAttribute(SocialConstants.ATTR_SOCIAL_LINK, socialLink);
-
String requestId = UUID.randomUUID().toString();
- socialRequestManager.addRequest(requestId, reqDetailsBuilder.build());
+ socialRequestManager.addRequest(requestId, RequestDetails.create(requestData).build());
boolean secureOnly = !realm.isSslNotRequired();
String cookiePath = Urls.socialBase(uriInfo.getBaseUri()).build().getPath();
logger.debug("creating cookie for social registration - name: {0} path: {1}", SocialConstants.SOCIAL_REGISTRATION_COOKIE,
@@ -241,7 +233,7 @@ public class SocialResource {
try {
AuthRequest authRequest = provider.getAuthUrl(config);
- RequestDetails socialRequest = RequestDetailsBuilder.create(providerId)
+ RequestDetails socialRequest = RequestDetails.create(providerId)
.putSocialAttributes(authRequest.getAttributes()).putClientAttribute("realmId", realmId)
.putClientAttribute("clientId", clientId).putClientAttribute("scope", scope)
.putClientAttribute("state", state).putClientAttribute("redirectUri", redirectUri).build();
@@ -285,7 +277,6 @@ public class SocialResource {
String scope = requestData.getClientAttribute("scope");
String state = requestData.getClientAttribute("state");
String redirectUri = requestData.getClientAttribute("redirectUri");
- SocialLinkModel socialLink = (SocialLinkModel)requestData.getSocialAttribute(SocialConstants.ATTR_SOCIAL_LINK);
Response response1 = tokenService.processRegisterImpl(clientId, scope, state, redirectUri, formData, true);
@@ -301,7 +292,7 @@ public class SocialResource {
// Normally shouldn't happen
throw new IllegalStateException("User " + username + " not found in the realm");
}
- realm.addSocialLink(user, socialLink);
+ realm.addSocialLink(user, new SocialLinkModel(requestData.getProviderId(), username));
// Expire cookie and invalidate requestData
String cookiePath = Urls.socialBase(uriInfo.getBaseUri()).build().getPath();
diff --git a/services/src/main/java/org/keycloak/services/resources/TokenService.java b/services/src/main/java/org/keycloak/services/resources/TokenService.java
index 2e3b2aa..d9690df 100755
--- a/services/src/main/java/org/keycloak/services/resources/TokenService.java
+++ b/services/src/main/java/org/keycloak/services/resources/TokenService.java
@@ -323,10 +323,6 @@ public class TokenService {
realm.updateCredential(user, credentials);
}
- for (RoleModel role : realm.getDefaultRoles()) {
- realm.grantRole(user, role);
- }
-
return null;
}
@@ -427,7 +423,7 @@ public class TokenService {
logger.debug("accessRequest SUCCESS");
AccessTokenResponse res = accessTokenResponse(realm.getPrivateKey(), accessCode.getToken());
- return Cors.add(request, Response.ok(res)).allowedOrigins(client.getWebOrigins()).build();
+ return Cors.add(request, Response.ok(res)).allowedOrigins(client).build();
}
protected AccessTokenResponse accessTokenResponse(PrivateKey privateKey, SkeletonKeyToken token) {
@@ -468,7 +464,7 @@ public class TokenService {
}
UserModel client = realm.getUser(clientId);
if (client == null) {
- logger.warn("Unknown login requester.");
+ logger.warn("Unknown login requester: " + clientId);
oauth.forwardToSecurityFailure("Unknown login requester.");
transaction.rollback();
return null;
diff --git a/services/src/test/java/org/keycloak/services/email/EmailSenderTest.java b/services/src/test/java/org/keycloak/services/email/EmailSenderTest.java
index d4c5a66..e749f16 100755
--- a/services/src/test/java/org/keycloak/services/email/EmailSenderTest.java
+++ b/services/src/test/java/org/keycloak/services/email/EmailSenderTest.java
@@ -55,7 +55,7 @@ public class EmailSenderTest {
}
@Test
- public void sendMail() throws AddressException, MessagingException, IOException {
+ public void sendMail() throws MessagingException, IOException {
emailSender.send("test@test.com", "Test subject", "Test body");
MimeMessage[] receivedMessages = greenMail.getReceivedMessages();
diff --git a/services/src/test/java/org/keycloak/test/AdapterTest.java b/services/src/test/java/org/keycloak/test/AdapterTest.java
index 210bdb7..1c86dfe 100755
--- a/services/src/test/java/org/keycloak/test/AdapterTest.java
+++ b/services/src/test/java/org/keycloak/test/AdapterTest.java
@@ -77,7 +77,7 @@ public class AdapterTest extends AbstractKeycloakTest {
Assert.assertEquals(realmModel.getPublicKeyPem(), "0234234");
Assert.assertEquals(realmModel.isAutomaticRegistrationAfterSocialLogin(), true);
Assert.assertEquals(1, realmModel.getDefaultRoles().size());
- Assert.assertEquals("foo", realmModel.getDefaultRoles().get(0).getName());
+ Assert.assertEquals("foo", realmModel.getDefaultRoles().get(0));
}
@Test
@@ -106,7 +106,7 @@ public class AdapterTest extends AbstractKeycloakTest {
Assert.assertEquals(realmModel.getPublicKeyPem(), "0234234");
Assert.assertEquals(realmModel.isAutomaticRegistrationAfterSocialLogin(), true);
Assert.assertEquals(1, realmModel.getDefaultRoles().size());
- Assert.assertEquals("foo", realmModel.getDefaultRoles().get(0).getName());
+ Assert.assertEquals("foo", realmModel.getDefaultRoles().get(0));
String id = realmModel.getId();
System.out.println("id: " + id);
diff --git a/services/src/test/java/org/keycloak/test/ApplicationModelTest.java b/services/src/test/java/org/keycloak/test/ApplicationModelTest.java
index 34f2e41..a5e2a01 100755
--- a/services/src/test/java/org/keycloak/test/ApplicationModelTest.java
+++ b/services/src/test/java/org/keycloak/test/ApplicationModelTest.java
@@ -45,6 +45,8 @@ public class ApplicationModelTest extends AbstractKeycloakServerTest {
application.setName("app-name");
application.addRole("role-1");
application.addRole("role-2");
+ application.addDefaultRole("role-1");
+ application.addDefaultRole("role-2");
application.getApplicationUser().addRedirectUri("redirect-1");
application.getApplicationUser().addRedirectUri("redirect-2");
@@ -83,6 +85,7 @@ public class ApplicationModelTest extends AbstractKeycloakServerTest {
Assert.assertEquals(expected.getName(), actual.getName());
Assert.assertEquals(expected.getBaseUrl(), actual.getBaseUrl());
Assert.assertEquals(expected.getManagementUrl(), actual.getManagementUrl());
+ Assert.assertEquals(expected.getDefaultRoles(), actual.getDefaultRoles());
UserModel auser = actual.getApplicationUser();
UserModel euser = expected.getApplicationUser();
diff --git a/services/src/test/java/org/keycloak/test/ModelTest.java b/services/src/test/java/org/keycloak/test/ModelTest.java
index 4db17bf..33eb405 100755
--- a/services/src/test/java/org/keycloak/test/ModelTest.java
+++ b/services/src/test/java/org/keycloak/test/ModelTest.java
@@ -85,7 +85,7 @@ public class ModelTest extends AbstractKeycloakServerTest {
Assert.assertEquals(expected.getPublicKeyPem(), actual.getPublicKeyPem());
Assert.assertEquals(expected.getPrivateKeyPem(), actual.getPrivateKeyPem());
- assertEquals(expected.getDefaultRoles(), actual.getDefaultRoles());
+ Assert.assertEquals(expected.getDefaultRoles(), actual.getDefaultRoles());
Assert.assertEquals(expected.getSmtpConfig(), actual.getSmtpConfig());
Assert.assertEquals(expected.getSocialConfig(), actual.getSocialConfig());
diff --git a/social/core/src/main/java/org/keycloak/social/AuthCallback.java b/social/core/src/main/java/org/keycloak/social/AuthCallback.java
index 8e21a21..d9c579a 100644
--- a/social/core/src/main/java/org/keycloak/social/AuthCallback.java
+++ b/social/core/src/main/java/org/keycloak/social/AuthCallback.java
@@ -28,11 +28,11 @@ import java.util.Map;
*/
public class AuthCallback {
- private Map<String, Object> attributes;
+ private Map<String, String> attributes;
private Map<String, String[]> queryParams;
- public AuthCallback(Map<String, Object> attributes, Map<String, String[]> queryParams) {
+ public AuthCallback(Map<String, String> attributes, Map<String, String[]> queryParams) {
this.attributes = attributes;
this.queryParams = queryParams;
}
diff --git a/social/core/src/main/java/org/keycloak/social/AuthRequest.java b/social/core/src/main/java/org/keycloak/social/AuthRequest.java
index 69731e9..481885d 100644
--- a/social/core/src/main/java/org/keycloak/social/AuthRequest.java
+++ b/social/core/src/main/java/org/keycloak/social/AuthRequest.java
@@ -21,7 +21,9 @@
*/
package org.keycloak.social;
+import javax.ws.rs.core.UriBuilder;
import java.net.URI;
+import java.util.HashMap;
import java.util.Map;
/**
@@ -33,9 +35,17 @@ public class AuthRequest {
private URI authUri;
- private Map<String, Object> attributes;
+ private Map<String, String> attributes;
- AuthRequest(String id, URI authUri, Map<String, Object> attributes) {
+ public static AuthRequestBuilder create(String id, String path) {
+ AuthRequestBuilder req = new AuthRequestBuilder();
+ req.id = id;
+ req.b = UriBuilder.fromUri(path);
+ req.attributes = new HashMap<String, String>();
+ return req;
+ }
+
+ private AuthRequest(String id, URI authUri, Map<String, String> attributes) {
this.id = id;
this.authUri = authUri;
this.attributes = attributes;
@@ -49,8 +59,35 @@ public class AuthRequest {
return authUri;
}
- public Map<String, Object> getAttributes() {
+ public Map<String, String> getAttributes() {
return attributes;
}
+ public static class AuthRequestBuilder {
+
+ private UriBuilder b;
+
+ private Map<String, String> attributes;
+
+ private String id;
+
+ private AuthRequestBuilder() {
+ }
+
+ public AuthRequestBuilder setQueryParam(String name, String value) {
+ b.queryParam(name, value);
+ return this;
+ }
+
+ public AuthRequestBuilder setAttribute(String name, String value) {
+ attributes.put(name, value);
+ return this;
+ }
+
+ public AuthRequest build() {
+ return new AuthRequest(id, b.build(), attributes);
+ }
+
+ }
+
}
diff --git a/social/core/src/main/java/org/keycloak/social/RequestDetails.java b/social/core/src/main/java/org/keycloak/social/RequestDetails.java
index f77f59b..0d87812 100644
--- a/social/core/src/main/java/org/keycloak/social/RequestDetails.java
+++ b/social/core/src/main/java/org/keycloak/social/RequestDetails.java
@@ -21,6 +21,7 @@
*/
package org.keycloak.social;
+import java.util.HashMap;
import java.util.Map;
/**
@@ -32,9 +33,27 @@ public class RequestDetails {
private Map<String, String> clientAttributes;
- private Map<String, Object> socialAttributes;
+ private Map<String, String> socialAttributes;
- RequestDetails(String providerId, Map<String, String> clientAttributes, Map<String, Object> socialAttributes) {
+ public static RequestDetailsBuilder create(String providerId) {
+ RequestDetailsBuilder req = new RequestDetailsBuilder();
+ req.providerId = providerId;
+ req.clientAttributes = new HashMap<String, String>();
+ req.socialAttributes = new HashMap<String, String>();
+ return req;
+ }
+
+ public static RequestDetailsBuilder create(RequestDetails from) {
+ RequestDetailsBuilder req = new RequestDetailsBuilder();
+ req.providerId = from.getProviderId();
+ req.clientAttributes = new HashMap<String, String>();
+ req.clientAttributes.putAll(from.getClientAttributes());
+ req.socialAttributes = new HashMap<String, String>();
+ req.socialAttributes.putAll(from.getSocialAttributes());
+ return req;
+ }
+
+ private RequestDetails(String providerId, Map<String, String> clientAttributes, Map<String, String> socialAttributes) {
this.providerId = providerId;
this.clientAttributes = clientAttributes;
this.socialAttributes = socialAttributes;
@@ -52,12 +71,50 @@ public class RequestDetails {
return clientAttributes;
}
- public Object getSocialAttribute(String name) {
+ public String getSocialAttribute(String name) {
return socialAttributes.get(name);
}
- public Map<String, Object> getSocialAttributes() {
+ public Map<String, String> getSocialAttributes() {
return socialAttributes;
}
+
+ public static class RequestDetailsBuilder {
+
+ private String providerId;
+
+ private Map<String, String> clientAttributes;
+
+ private Map<String, String> socialAttributes;
+
+ private RequestDetailsBuilder() {
+ }
+
+ public RequestDetailsBuilder putClientAttribute(String name, String value) {
+ clientAttributes.put(name, value);
+ return this;
+ }
+
+ public RequestDetailsBuilder putClientAttributes(Map<String, String> attributes) {
+ clientAttributes.putAll(attributes);
+ return this;
+ }
+
+ public RequestDetailsBuilder putSocialAttribute(String name, String value) {
+ socialAttributes.put(name, value);
+ return this;
+ }
+
+ public RequestDetailsBuilder putSocialAttributes(Map<String, String> attributes) {
+ socialAttributes.putAll(attributes);
+ return this;
+ }
+
+ public RequestDetails build() {
+ return new RequestDetails(providerId, clientAttributes, socialAttributes);
+ }
+
+ }
+
}
diff --git a/social/core/src/main/java/org/keycloak/social/SocialProviderConfig.java b/social/core/src/main/java/org/keycloak/social/SocialProviderConfig.java
index d6d1276..4989861 100644
--- a/social/core/src/main/java/org/keycloak/social/SocialProviderConfig.java
+++ b/social/core/src/main/java/org/keycloak/social/SocialProviderConfig.java
@@ -50,8 +50,4 @@ public class SocialProviderConfig {
return secret;
}
- public void setSecret(String secret) {
- this.secret = secret;
- }
-
}
diff --git a/social/facebook/src/main/java/org/keycloak/social/facebook/FacebookProvider.java b/social/facebook/src/main/java/org/keycloak/social/facebook/FacebookProvider.java
index 2a7b488..ac9bc86 100755
--- a/social/facebook/src/main/java/org/keycloak/social/facebook/FacebookProvider.java
+++ b/social/facebook/src/main/java/org/keycloak/social/facebook/FacebookProvider.java
@@ -4,7 +4,6 @@ import org.jboss.resteasy.client.jaxrs.ResteasyClient;
import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder;
import org.keycloak.social.AuthCallback;
import org.keycloak.social.AuthRequest;
-import org.keycloak.social.AuthRequestBuilder;
import org.keycloak.social.SocialProvider;
import org.keycloak.social.SocialProviderConfig;
import org.keycloak.social.SocialProviderException;
@@ -45,13 +44,9 @@ public class FacebookProvider implements SocialProvider {
public AuthRequest getAuthUrl(SocialProviderConfig config) throws SocialProviderException {
String state = UUID.randomUUID().toString();
- AuthRequestBuilder b = AuthRequestBuilder.create(state, AUTHENTICATION_ENDPOINT_URL).setQueryParam("client_id", config.getKey())
+ return AuthRequest.create(state, AUTHENTICATION_ENDPOINT_URL).setQueryParam("client_id", config.getKey())
.setQueryParam("response_type", DEFAULT_RESPONSE_TYPE).setQueryParam("scope", DEFAULT_SCOPE)
- .setQueryParam("redirect_uri", config.getCallbackUrl()).setQueryParam("state", state);
-
- b.setAttribute("state", state);
-
- return b.build();
+ .setQueryParam("redirect_uri", config.getCallbackUrl()).setQueryParam("state", state).setAttribute("state", state).build();
}
@Override
diff --git a/social/google/src/main/java/org/keycloak/social/google/GoogleProvider.java b/social/google/src/main/java/org/keycloak/social/google/GoogleProvider.java
index 2e2f186..2f743b7 100755
--- a/social/google/src/main/java/org/keycloak/social/google/GoogleProvider.java
+++ b/social/google/src/main/java/org/keycloak/social/google/GoogleProvider.java
@@ -21,6 +21,15 @@
*/
package org.keycloak.social.google;
+import java.util.UUID;
+
+import org.keycloak.social.AuthCallback;
+import org.keycloak.social.AuthRequest;
+import org.keycloak.social.SocialProvider;
+import org.keycloak.social.SocialProviderConfig;
+import org.keycloak.social.SocialProviderException;
+import org.keycloak.social.SocialUser;
+
import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeTokenRequest;
import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
import com.google.api.client.googleapis.auth.oauth2.GoogleTokenResponse;
@@ -29,15 +38,6 @@ import com.google.api.client.json.jackson.JacksonFactory;
import com.google.api.services.oauth2.Oauth2;
import com.google.api.services.oauth2.model.Tokeninfo;
import com.google.api.services.oauth2.model.Userinfo;
-import org.keycloak.social.AuthCallback;
-import org.keycloak.social.AuthRequest;
-import org.keycloak.social.AuthRequestBuilder;
-import org.keycloak.social.SocialProvider;
-import org.keycloak.social.SocialProviderConfig;
-import org.keycloak.social.SocialProviderException;
-import org.keycloak.social.SocialUser;
-
-import java.util.UUID;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@@ -62,14 +62,10 @@ public class GoogleProvider implements SocialProvider {
@Override
public AuthRequest getAuthUrl(SocialProviderConfig config) throws SocialProviderException {
String state = UUID.randomUUID().toString();
-
- AuthRequestBuilder b = AuthRequestBuilder.create(state, AUTH_PATH).setQueryParam("client_id", config.getKey())
- .setQueryParam("response_type", DEFAULT_RESPONSE_TYPE).setQueryParam("scope", DEFAULT_SCOPE)
- .setQueryParam("redirect_uri", config.getCallbackUrl()).setQueryParam("state", state);
- b.setAttribute("state", state);
-
- return b.build();
+ return AuthRequest.create(state, AUTH_PATH).setQueryParam("client_id", config.getKey())
+ .setQueryParam("response_type", DEFAULT_RESPONSE_TYPE).setQueryParam("scope", DEFAULT_SCOPE)
+ .setQueryParam("redirect_uri", config.getCallbackUrl()).setQueryParam("state", state).setAttribute("state", state).build();
}
@Override
diff --git a/social/twitter/src/main/java/org/keycloak/social/twitter/TwitterProvider.java b/social/twitter/src/main/java/org/keycloak/social/twitter/TwitterProvider.java
index 3ba2e04..8f7a508 100755
--- a/social/twitter/src/main/java/org/keycloak/social/twitter/TwitterProvider.java
+++ b/social/twitter/src/main/java/org/keycloak/social/twitter/TwitterProvider.java
@@ -23,7 +23,6 @@ package org.keycloak.social.twitter;
import org.keycloak.social.AuthCallback;
import org.keycloak.social.AuthRequest;
-import org.keycloak.social.AuthRequestBuilder;
import org.keycloak.social.SocialProvider;
import org.keycloak.social.SocialProviderConfig;
import org.keycloak.social.SocialProviderException;
@@ -50,7 +49,7 @@ public class TwitterProvider implements SocialProvider {
RequestToken requestToken = twitter.getOAuthRequestToken(request.getCallbackUrl());
- return AuthRequestBuilder.create(requestToken.getToken(), requestToken.getAuthenticationURL())
+ return AuthRequest.create(requestToken.getToken(), requestToken.getAuthenticationURL())
.setAttribute("token", requestToken.getToken()).setAttribute("tokenSecret", requestToken.getTokenSecret())
.build();
} catch (Exception e) {
testsuite/integration/pom.xml 4(+4 -0)
diff --git a/testsuite/integration/pom.xml b/testsuite/integration/pom.xml
index 32a0989..ffbe981 100755
--- a/testsuite/integration/pom.xml
+++ b/testsuite/integration/pom.xml
@@ -192,6 +192,10 @@
<artifactId>selenium-java</artifactId>
</dependency>
<dependency>
+ <groupId>org.seleniumhq.selenium</groupId>
+ <artifactId>selenium-chrome-driver</artifactId>
+ </dependency>
+ <dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongo-java-driver</artifactId>
</dependency>
diff --git a/testsuite/integration/src/main/resources/log4j.properties b/testsuite/integration/src/main/resources/log4j.properties
index b3c1c5c..bc27773 100644
--- a/testsuite/integration/src/main/resources/log4j.properties
+++ b/testsuite/integration/src/main/resources/log4j.properties
@@ -1,5 +1,7 @@
-log4j.rootLogger=debug, stdout
+log4j.rootLogger=info, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
-log4j.appender.stdout.layout.ConversionPattern=%d{HH:mm:ss,SSS} %-5p [%c] %m%n
\ No newline at end of file
+log4j.appender.stdout.layout.ConversionPattern=%d{HH:mm:ss,SSS} %-5p [%c] %m%n
+
+log4j.logger.org.keycloak=debug
\ No newline at end of file
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/account/ProfileTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/account/ProfileTest.java
new file mode 100755
index 0000000..23ac614
--- /dev/null
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/account/ProfileTest.java
@@ -0,0 +1,245 @@
+package org.keycloak.testsuite.account;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.http.HttpHeaders;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.impl.client.DefaultHttpClient;
+import org.json.JSONObject;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.keycloak.models.ApplicationModel;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.services.managers.RealmManager;
+import org.keycloak.testsuite.Constants;
+import org.keycloak.testsuite.OAuthClient;
+import org.keycloak.testsuite.pages.AccountUpdateProfilePage;
+import org.keycloak.testsuite.pages.LoginPage;
+import org.keycloak.testsuite.pages.OAuthGrantPage;
+import org.keycloak.testsuite.rule.KeycloakRule;
+import org.keycloak.testsuite.rule.WebResource;
+import org.keycloak.testsuite.rule.WebRule;
+import org.openqa.selenium.JavascriptExecutor;
+import org.openqa.selenium.WebDriver;
+
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.UriBuilder;
+import java.io.IOException;
+import java.net.URI;
+import java.util.Collections;
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class ProfileTest {
+
+ @ClassRule
+ public static KeycloakRule keycloakRule = new KeycloakRule(new KeycloakRule.KeycloakSetup() {
+ @Override
+ public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+ UserModel user = appRealm.getUser("test-user@localhost");
+ user.setFirstName("First");
+ user.setLastName("Last");
+ user.setAttribute("key1", "value1");
+ user.setAttribute("key2", "value2");
+
+ ApplicationModel accountApp = appRealm.getApplicationNameMap().get(org.keycloak.models.Constants.ACCOUNT_APPLICATION);
+
+ ApplicationModel app = appRealm.getApplicationNameMap().get("test-app");
+ accountApp.addScopeMapping(app.getApplicationUser(), org.keycloak.models.Constants.ACCOUNT_PROFILE_ROLE);
+
+ app.getApplicationUser().addWebOrigin("http://localtest.me:8081");
+
+ UserModel thirdParty = appRealm.getUser("third-party");
+ accountApp.addScopeMapping(thirdParty, org.keycloak.models.Constants.ACCOUNT_PROFILE_ROLE);
+ }
+ });
+
+ @Rule
+ public WebRule webRule = new WebRule(this);
+
+ @WebResource
+ protected WebDriver driver;
+
+ @WebResource
+ protected OAuthClient oauth;
+
+ @WebResource
+ protected AccountUpdateProfilePage profilePage;
+
+ @WebResource
+ protected LoginPage loginPage;
+
+ @WebResource
+ protected OAuthGrantPage grantPage;
+
+ private List<String> defaultRoles;
+
+ @Test
+ public void getProfile() throws Exception {
+ oauth.doLogin("test-user@localhost", "password");
+
+ String code = oauth.getCurrentQuery().get("code");
+ String token = oauth.doAccessTokenRequest(code, "password").getAccessToken();
+
+ HttpResponse response = doGetProfile(token, null);
+ assertEquals(200, response.getStatusLine().getStatusCode());
+ JSONObject profile = new JSONObject(IOUtils.toString(response.getEntity().getContent()));
+
+ assertEquals("test-user@localhost", profile.getString("username"));
+ assertEquals("test-user@localhost", profile.getString("email"));
+ assertEquals("First", profile.getString("firstName"));
+ assertEquals("Last", profile.getString("lastName"));
+
+ JSONObject attributes = profile.getJSONObject("attributes");
+ assertEquals(2, attributes.length());
+ assertEquals("value1", attributes.getString("key1"));
+ assertEquals("value2", attributes.getString("key2"));
+ }
+
+ @Test
+ public void getProfileCors() throws Exception {
+ oauth.doLogin("test-user@localhost", "password");
+
+ String code = oauth.getCurrentQuery().get("code");
+ String token = oauth.doAccessTokenRequest(code, "password").getAccessToken();
+
+ driver.navigate().to("http://localtest.me:8081/app");
+
+ String[] response = doGetProfileJs(token);
+ assertEquals("200", response[0]);
+ }
+
+ @Test
+ public void getProfileCorsInvalidOrigin() throws Exception {
+ oauth.doLogin("test-user@localhost", "password");
+
+ String code = oauth.getCurrentQuery().get("code");
+ String token = oauth.doAccessTokenRequest(code, "password").getAccessToken();
+
+ driver.navigate().to("http://invalid.localtest.me:8081");
+
+ try {
+ doGetProfileJs(token);
+ fail("Expected failure");
+ } catch (Throwable t) {
+ }
+ }
+
+ @Test
+ public void getProfileCookieAuth() throws Exception {
+ profilePage.open();
+ loginPage.login("test-user@localhost", "password");
+
+ String[] response = doGetProfileJs(null);
+ assertEquals("200", response[0]);
+
+ JSONObject profile = new JSONObject(response[1]);
+ assertEquals("test-user@localhost", profile.getString("username"));
+ }
+
+ @Test
+ public void getProfileNoAuth() throws Exception {
+ HttpResponse response = doGetProfile(null, null);
+ assertEquals(403, response.getStatusLine().getStatusCode());
+ }
+
+ @Test
+ public void getProfileNoAccess() throws Exception {
+ try {
+ keycloakRule.configure(new KeycloakRule.KeycloakSetup() {
+ @Override
+ public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+ ApplicationModel app = appRealm.getApplicationNameMap().get(org.keycloak.models.Constants.ACCOUNT_APPLICATION);
+ defaultRoles = app.getDefaultRoles();
+ app.updateDefaultRoles(new String[0]);
+ }
+ });
+
+ oauth.doLogin("test-user@localhost", "password");
+
+ String code = oauth.getCurrentQuery().get("code");
+ String token = oauth.doAccessTokenRequest(code, "password").getAccessToken();
+
+ HttpResponse response = doGetProfile(token, null);
+ assertEquals(403, response.getStatusLine().getStatusCode());
+ } finally {
+ keycloakRule.configure(new KeycloakRule.KeycloakSetup() {
+ @Override
+ public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+ appRealm.getApplicationNameMap().get(org.keycloak.models.Constants.ACCOUNT_APPLICATION).updateDefaultRoles((String[]) defaultRoles.toArray(new String[0]));
+ }
+ });
+ }
+ }
+
+ @Test
+ public void getProfileOAuthClient() throws Exception {
+ oauth.addScope(org.keycloak.models.Constants.ACCOUNT_APPLICATION, org.keycloak.models.Constants.ACCOUNT_PROFILE_ROLE);
+ oauth.clientId("third-party");
+ oauth.doLoginGrant("test-user@localhost", "password");
+
+ grantPage.accept();
+
+ String token = oauth.doAccessTokenRequest(oauth.getCurrentQuery().get("code"), "password").getAccessToken();
+ HttpResponse response = doGetProfile(token, null);
+
+ assertEquals(200, response.getStatusLine().getStatusCode());
+ JSONObject profile = new JSONObject(IOUtils.toString(response.getEntity().getContent()));
+
+ assertEquals("test-user@localhost", profile.getString("username"));
+ }
+
+ @Test
+ public void getProfileOAuthClientNoScope() throws Exception {
+ oauth.addScope(org.keycloak.models.Constants.ACCOUNT_APPLICATION);
+ oauth.clientId("third-party");
+ oauth.doLoginGrant("test-user@localhost", "password");
+
+ String token = oauth.doAccessTokenRequest(oauth.getCurrentQuery().get("code"), "password").getAccessToken();
+ HttpResponse response = doGetProfile(token, null);
+
+ assertEquals(403, response.getStatusLine().getStatusCode());
+ }
+
+ private URI getAccountURI() {
+ return UriBuilder.fromUri(Constants.AUTH_SERVER_ROOT + "/rest/realms/" + oauth.getRealm() + "/account").build();
+ }
+
+ private HttpResponse doGetProfile(String token, String origin) throws IOException {
+ HttpClient client = new DefaultHttpClient();
+ HttpGet get = new HttpGet(UriBuilder.fromUri(getAccountURI()).build());
+ if (token != null) {
+ get.setHeader(HttpHeaders.AUTHORIZATION, "bearer " + token);
+ }
+ if (origin != null) {
+ get.setHeader("Origin", origin);
+ }
+ get.setHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON);
+ return client.execute(get);
+ }
+
+ private String[] doGetProfileJs(String token) {
+ StringBuilder sb = new StringBuilder();
+ sb.append("var req = new XMLHttpRequest();\n");
+ sb.append("req.open('GET', '" + getAccountURI().toString() + "', false);\n");
+ if (token != null) {
+ sb.append("req.setRequestHeader('Authorization', 'Bearer " + token + "');\n");
+ }
+ sb.append("req.setRequestHeader('Accept', 'application/json');\n");
+ sb.append("req.send(null);\n");
+ sb.append("return req.status + '///' + req.responseText;\n");
+
+ JavascriptExecutor js = (JavascriptExecutor) driver;
+ String response = (String) js.executeScript(sb.toString());
+ return response.split("///");
+ }
+}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionMultipleActionsTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionMultipleActionsTest.java
index eca156e..917d047 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionMultipleActionsTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionMultipleActionsTest.java
@@ -76,7 +76,7 @@ public class RequiredActionMultipleActionsTest {
protected LoginUpdateProfilePage updateProfilePage;
@Test
- public void updateProfileAndPassword() {
+ public void updateProfileAndPassword() throws Exception {
loginPage.open();
loginPage.login("test-user@localhost", "password");
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionResetPasswordTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionResetPasswordTest.java
index b8a7b7a..28197d0 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionResetPasswordTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionResetPasswordTest.java
@@ -81,7 +81,7 @@ public class RequiredActionResetPasswordTest {
protected LoginPasswordUpdatePage changePasswordPage;
@Test
- public void tempPassword() {
+ public void tempPassword() throws Exception {
loginPage.open();
loginPage.login("test-user@localhost", "password");
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/DummySocial.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/DummySocial.java
index d296498..bab9f43 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/DummySocial.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/DummySocial.java
@@ -2,7 +2,6 @@ package org.keycloak.testsuite;
import org.keycloak.social.AuthCallback;
import org.keycloak.social.AuthRequest;
-import org.keycloak.social.AuthRequestBuilder;
import org.keycloak.social.SocialProvider;
import org.keycloak.social.SocialProviderConfig;
import org.keycloak.social.SocialProviderException;
@@ -23,12 +22,8 @@ public class DummySocial implements SocialProvider {
public AuthRequest getAuthUrl(SocialProviderConfig config) throws SocialProviderException {
String state = UUID.randomUUID().toString();
- AuthRequestBuilder b = AuthRequestBuilder.create(state, AUTH_PATH).setQueryParam("response_type", "token")
- .setQueryParam("redirect_uri", config.getCallbackUrl()).setQueryParam("state", state);
-
- b.setAttribute("state", state);
-
- return b.build();
+ return AuthRequest.create(state, AUTH_PATH).setQueryParam("response_type", "token")
+ .setQueryParam("redirect_uri", config.getCallbackUrl()).setQueryParam("state", state).setAttribute("state", state).build();
}
@Override
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/AccountTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/AccountTest.java
index b035945..b38411e 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/AccountTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/AccountTest.java
@@ -21,29 +21,26 @@
*/
package org.keycloak.testsuite.forms;
-import org.junit.After;
-import org.junit.Assert;
-import org.junit.ClassRule;
-import org.junit.Rule;
-import org.junit.Test;
-import org.keycloak.models.RealmModel;
-import org.keycloak.models.UserCredentialModel;
-import org.keycloak.models.UserModel;
+import org.apache.http.HttpResponse;
+import org.junit.*;
+import org.keycloak.models.*;
import org.keycloak.models.utils.TimeBasedOTP;
import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.testsuite.OAuthClient;
-import org.keycloak.testsuite.pages.AccountPasswordPage;
-import org.keycloak.testsuite.pages.AccountTotpPage;
-import org.keycloak.testsuite.pages.AccountUpdateProfilePage;
-import org.keycloak.testsuite.pages.AppPage;
+import org.keycloak.testsuite.pages.*;
import org.keycloak.testsuite.pages.AppPage.RequestType;
-import org.keycloak.testsuite.pages.LoginPage;
import org.keycloak.testsuite.rule.KeycloakRule;
import org.keycloak.testsuite.rule.KeycloakRule.KeycloakSetup;
import org.keycloak.testsuite.rule.WebResource;
import org.keycloak.testsuite.rule.WebRule;
import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.support.FindBy;
+
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@@ -77,8 +74,13 @@ public class AccountTest {
@WebResource
protected AccountTotpPage totpPage;
+ @WebResource
+ protected ErrorPage errorPage;
+
private TimeBasedOTP totp = new TimeBasedOTP();
+ private List<String> defaultRoles;
+
@After
public void after() {
keycloakRule.configure(new KeycloakSetup() {
@@ -185,4 +187,31 @@ public class AccountTest {
Assert.assertTrue(driver.getPageSource().contains("Remove Google"));
}
+ @Test
+ public void changeProfileNoAccess() throws Exception {
+ try {
+ keycloakRule.configure(new KeycloakRule.KeycloakSetup() {
+ @Override
+ public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+ ApplicationModel app = appRealm.getApplicationNameMap().get(Constants.ACCOUNT_APPLICATION);
+ defaultRoles = app.getDefaultRoles();
+ app.updateDefaultRoles(new String[0]);
+ }
+ });
+
+ profilePage.open();
+ loginPage.login("test-user@localhost", "password");
+
+ Assert.assertTrue(errorPage.isCurrent());
+ Assert.assertEquals("No access", errorPage.getError());
+ } finally {
+ keycloakRule.configure(new KeycloakRule.KeycloakSetup() {
+ @Override
+ public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+ appRealm.getApplicationNameMap().get(org.keycloak.models.Constants.ACCOUNT_APPLICATION).updateDefaultRoles((String[]) defaultRoles.toArray(new String[0]));
+ }
+ });
+ }
+ }
+
}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LoginTotpTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LoginTotpTest.java
index c637062..a86392b 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LoginTotpTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LoginTotpTest.java
@@ -93,7 +93,7 @@ public class LoginTotpTest {
}
@Test
- public void loginWithTotpFailure() {
+ public void loginWithTotpFailure() throws Exception {
loginPage.open();
loginPage.login("test-user@localhost", "password");
@@ -106,7 +106,7 @@ public class LoginTotpTest {
}
@Test
- public void loginWithTotpSuccess() {
+ public void loginWithTotpSuccess() throws Exception {
loginPage.open();
loginPage.login("test-user@localhost", "password");
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/OAuthGrantTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/OAuthGrantTest.java
index 3a60ae6..e5d7ef6 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/OAuthGrantTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/OAuthGrantTest.java
@@ -21,13 +21,16 @@
*/
package org.keycloak.testsuite.oauth;
+import java.io.IOException;
+import java.util.Map;
+
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
+import org.keycloak.representations.SkeletonKeyToken;
import org.keycloak.testsuite.OAuthClient;
-import org.keycloak.testsuite.OAuthGrantServlet;
import org.keycloak.testsuite.pages.LoginPage;
import org.keycloak.testsuite.pages.OAuthGrantPage;
import org.keycloak.testsuite.rule.KeycloakRule;
@@ -35,8 +38,6 @@ import org.keycloak.testsuite.rule.WebResource;
import org.keycloak.testsuite.rule.WebRule;
import org.openqa.selenium.WebDriver;
-import java.io.IOException;
-
/**
* @author <a href="mailto:vrockai@redhat.com">Viliam Rockai</a>
*/
@@ -45,11 +46,6 @@ public class OAuthGrantTest {
@ClassRule
public static KeycloakRule keycloakRule = new KeycloakRule();
- @BeforeClass
- public static void before() {
- keycloakRule.deployServlet("grant", "/grant", OAuthGrantServlet.class);
- }
-
@Rule
public WebRule webRule = new WebRule(this);
@@ -65,28 +61,13 @@ public class OAuthGrantTest {
@WebResource
protected OAuthGrantPage grantPage;
- private static String GRANT_APP_URL = "http://localhost:8081/grant/";
-
- private static String ACCESS_GRANTED = "Access rights granted.";
- private static String ACCESS_NOT_GRANTED = "Access rights not granted.";
-
private static String ROLE_USER = "Have User privileges";
private static String ROLE_CUSTOMER = "Have Customer User privileges";
- private static String GRANT_ROLE = "user";
- private static String GRANT_APP = "test-app";
- private static String GRANT_APP_ROLE = "customer-user";
-
@Test
public void oauthGrantAcceptTest() throws IOException {
-
- driver.navigate().to(GRANT_APP_URL);
-
- Assert.assertFalse(driver.getPageSource().contains(ACCESS_GRANTED));
- Assert.assertFalse(driver.getPageSource().contains(ACCESS_NOT_GRANTED));
-
- loginPage.isCurrent();
- loginPage.login("test-user@localhost", "password");
+ oauth.clientId("third-party");
+ oauth.doLoginGrant("test-user@localhost", "password");
grantPage.assertCurrent();
Assert.assertTrue(driver.getPageSource().contains(ROLE_USER));
@@ -94,23 +75,50 @@ public class OAuthGrantTest {
grantPage.accept();
- Assert.assertTrue(driver.getPageSource().contains(ACCESS_GRANTED));
- Assert.assertFalse(driver.getPageSource().contains(ACCESS_NOT_GRANTED));
+ Assert.assertTrue(oauth.getCurrentQuery().containsKey("code"));
+ OAuthClient.AccessTokenResponse accessToken = oauth.doAccessTokenRequest(oauth.getCurrentQuery().get("code"), "password");
+
+ SkeletonKeyToken token = oauth.verifyToken(accessToken.getAccessToken());
- Assert.assertTrue(driver.getPageSource().contains("Role:"+ GRANT_ROLE +"."));
- Assert.assertTrue(driver.getPageSource().contains("App:"+ GRANT_APP +";"+ GRANT_APP_ROLE +"."));
+ SkeletonKeyToken.Access realmAccess = token.getRealmAccess();
+ Assert.assertEquals(1, realmAccess.getRoles().size());
+ Assert.assertTrue(realmAccess.isUserInRole("user"));
+
+ Map<String,SkeletonKeyToken.Access> resourceAccess = token.getResourceAccess();
+ Assert.assertEquals(1, resourceAccess.size());
+ Assert.assertEquals(1, resourceAccess.get("test-app").getRoles().size());
+ Assert.assertTrue(resourceAccess.get("test-app").isUserInRole("customer-user"));
}
@Test
- public void oauthGrantCancelTest() throws IOException {
+ public void oauthGrantAcceptTestWithScope() throws IOException {
+ oauth.addScope("test-app", "customer-user");
+ oauth.clientId("third-party");
+ oauth.doLoginGrant("test-user@localhost", "password");
+
+ grantPage.assertCurrent();
+ Assert.assertTrue(driver.getPageSource().contains(ROLE_CUSTOMER));
+
+ grantPage.accept();
+
+ Assert.assertTrue(oauth.getCurrentQuery().containsKey("code"));
+ OAuthClient.AccessTokenResponse accessToken = oauth.doAccessTokenRequest(oauth.getCurrentQuery().get("code"), "password");
+
+ SkeletonKeyToken token = oauth.verifyToken(accessToken.getAccessToken());
- driver.navigate().to(GRANT_APP_URL);
+ SkeletonKeyToken.Access realmAccess = token.getRealmAccess();
+ Assert.assertNull(realmAccess);
- Assert.assertFalse(driver.getPageSource().contains(ACCESS_GRANTED));
- Assert.assertFalse(driver.getPageSource().contains(ACCESS_NOT_GRANTED));
+ Map<String,SkeletonKeyToken.Access> resourceAccess = token.getResourceAccess();
+ Assert.assertEquals(1, resourceAccess.size());
+ Assert.assertEquals(1, resourceAccess.get("test-app").getRoles().size());
+ Assert.assertTrue(resourceAccess.get("test-app").isUserInRole("customer-user"));
+ }
- loginPage.isCurrent();
- loginPage.login("test-user@localhost", "password");
+ @Test
+ public void oauthGrantCancelTest() throws IOException {
+ oauth.clientId("third-party");
+ oauth.doLoginGrant("test-user@localhost", "password");
grantPage.assertCurrent();
Assert.assertTrue(driver.getPageSource().contains(ROLE_USER));
@@ -118,7 +126,7 @@ public class OAuthGrantTest {
grantPage.cancel();
- Assert.assertFalse(driver.getPageSource().contains(ACCESS_GRANTED));
- Assert.assertTrue(driver.getPageSource().contains(ACCESS_NOT_GRANTED));
+ Assert.assertTrue(oauth.getCurrentQuery().containsKey("error"));
+ Assert.assertEquals("access_denied", oauth.getCurrentQuery().get("error"));
}
}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/OAuthClient.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/OAuthClient.java
index 4522d9e..c69208f 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/OAuthClient.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/OAuthClient.java
@@ -30,10 +30,14 @@ import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URLEncodedUtils;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
+import org.jboss.resteasy.jose.Base64Url;
+import org.jboss.resteasy.jwt.JsonSerialization;
import org.jboss.resteasy.security.PemUtils;
import org.json.JSONObject;
import org.junit.Assert;
import org.keycloak.RSATokenVerifier;
+import org.keycloak.VerificationException;
+import org.keycloak.representations.SkeletonKeyScope;
import org.keycloak.representations.SkeletonKeyToken;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
@@ -67,17 +71,21 @@ public class OAuthClient {
private String redirectUri = "http://localhost:8081/app/auth";
- private String scope;
+ private SkeletonKeyScope scope;
private String state;
private PublicKey realmPublicKey;
- public OAuthClient(WebDriver driver) throws Exception {
+ public OAuthClient(WebDriver driver) {
this.driver = driver;
- JSONObject realmJson = new JSONObject(IOUtils.toString(getClass().getResourceAsStream("/testrealm.json")));
- realmPublicKey = PemUtils.decodePublicKey(realmJson.getString("publicKey"));
+ try {
+ JSONObject realmJson = new JSONObject(IOUtils.toString(getClass().getResourceAsStream("/testrealm.json")));
+ realmPublicKey = PemUtils.decodePublicKey(realmJson.getString("publicKey"));
+ } catch (Exception e) {
+ throw new RuntimeException("Failed to retrieve realm public key", e);
+ }
}
public AuthorizationCodeResponse doLogin(String username, String password) {
@@ -90,7 +98,15 @@ public class OAuthClient {
return new AuthorizationCodeResponse(this);
}
- public AccessTokenResponse doAccessTokenRequest(String code, String password) throws Exception {
+ public void doLoginGrant(String username, String password) {
+ openLoginForm();
+
+ driver.findElement(By.id("username")).sendKeys(username);
+ driver.findElement(By.id("password")).sendKeys(password);
+ driver.findElement(By.cssSelector("input[type=\"submit\"]")).click();
+ }
+
+ public AccessTokenResponse doAccessTokenRequest(String code, String password) {
HttpClient client = new DefaultHttpClient();
HttpPost post = new HttpPost(getAccessTokenUrl());
@@ -114,27 +130,19 @@ public class OAuthClient {
UrlEncodedFormEntity formEntity = new UrlEncodedFormEntity(parameters, Charset.forName("UTF-8"));
post.setEntity(formEntity);
- return new AccessTokenResponse(client.execute(post));
- }
-
- public SkeletonKeyToken verifyToken(String token) throws Exception {
- return RSATokenVerifier.verifyToken(token, realmPublicKey, realm);
- }
-
- public boolean isAuthorizationResponse() {
- return getCurrentRequest().equals(redirectUri) && getCurrentQuery().containsKey("code");
- }
-
- public String getState() {
- return state;
- }
-
- public String getClientId() {
- return clientId;
+ try {
+ return new AccessTokenResponse(client.execute(post));
+ } catch (Exception e) {
+ throw new RuntimeException("Failed to retrieve access token", e);
+ }
}
- public String getResponseType() {
- return responseType;
+ public SkeletonKeyToken verifyToken(String token) {
+ try {
+ return RSATokenVerifier.verifyToken(token, realmPublicKey, realm);
+ } catch (VerificationException e) {
+ throw new RuntimeException("Failed to verify token", e);
+ }
}
public String getCurrentRequest() {
@@ -174,10 +182,6 @@ public class OAuthClient {
return redirectUri;
}
- public String getScope() {
- return scope;
- }
-
public String getLoginFormUrl() {
UriBuilder b = UriBuilder.fromUri(baseUrl + "/realms/" + realm + "/tokens/login");
if (responseType != null) {
@@ -190,7 +194,12 @@ public class OAuthClient {
b.queryParam("redirect_uri", redirectUri);
}
if (scope != null) {
- b.queryParam("scope", scope);
+ try {
+
+ b.queryParam("scope", Base64Url.encode(JsonSerialization.toByteArray(scope, false)));
+ } catch (Exception e) {
+ throw new RuntimeException("Failed to serialize scope", e);
+ }
}
if (state != null) {
b.queryParam("state", state);
@@ -223,8 +232,11 @@ public class OAuthClient {
return this;
}
- public OAuthClient scope(String scope) {
- this.scope = scope;
+ public OAuthClient addScope(String resource, String... roles) {
+ if (scope == null) {
+ scope = new SkeletonKeyScope();
+ }
+ scope.addAll(resource, roles);
return this;
}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AbstractPage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AbstractPage.java
index 1183ece..c17b86a 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AbstractPage.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AbstractPage.java
@@ -41,6 +41,6 @@ public abstract class AbstractPage {
abstract boolean isCurrent();
- abstract void open();
+ abstract void open() throws Exception;
}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/WebRule.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/WebRule.java
index ae12ea9..ef37e21 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/WebRule.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/WebRule.java
@@ -54,6 +54,7 @@ public class WebRule extends ExternalResource {
if (browser.equals("htmlunit")) {
HtmlUnitDriver d = new HtmlUnitDriver();
+ d.getWebClient().getOptions().setJavaScriptEnabled(true);
d.getWebClient().getOptions().setCssEnabled(false);
driver = d;
} else if (browser.equals("chrome")) {
diff --git a/testsuite/performance/src/test/java/org/keycloak/testsuite/performance/CreateUsersWorker.java b/testsuite/performance/src/test/java/org/keycloak/testsuite/performance/CreateUsersWorker.java
index ece002b..12fced6 100755
--- a/testsuite/performance/src/test/java/org/keycloak/testsuite/performance/CreateUsersWorker.java
+++ b/testsuite/performance/src/test/java/org/keycloak/testsuite/performance/CreateUsersWorker.java
@@ -78,13 +78,6 @@ public class CreateUsersWorker implements Worker {
user.setEmail(username + "@email.com");
}
- // Adding default roles of realm to user
- if (addDefaultRoles) {
- for (RoleModel role : realm.getDefaultRoles()) {
- realm.grantRole(user, role);
- }
- }
-
// Creating password (will be same as username)
if (addPassword) {
UserCredentialModel password = new UserCredentialModel();