keycloak-aplcache

composite UI

2/1/2014 12:39:54 AM

Details

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 401be0c..aa32945 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
@@ -187,6 +187,12 @@ module.config([ '$routeProvider', function($routeProvider) {
                 },
                 role : function() {
                     return {};
+                },
+                roles : function(RoleListLoader) {
+                    return RoleListLoader();
+                },
+                applications : function(ApplicationListLoader) {
+                    return ApplicationListLoader();
                 }
             },
             controller : 'RoleDetailCtrl'
@@ -199,6 +205,12 @@ module.config([ '$routeProvider', function($routeProvider) {
                 },
                 role : function(RoleLoader) {
                     return RoleLoader();
+                },
+                roles : function(RoleListLoader) {
+                    return RoleListLoader();
+                },
+                applications : function(ApplicationListLoader) {
+                    return ApplicationListLoader();
                 }
             },
             controller : 'RoleDetailCtrl'
@@ -227,6 +239,12 @@ module.config([ '$routeProvider', function($routeProvider) {
                 },
                 role : function() {
                     return {};
+                },
+                roles : function(RoleListLoader) {
+                    return RoleListLoader();
+                },
+                applications : function(ApplicationListLoader) {
+                    return ApplicationListLoader();
                 }
             },
             controller : 'ApplicationRoleDetailCtrl'
@@ -242,6 +260,12 @@ module.config([ '$routeProvider', function($routeProvider) {
                 },
                 role : function(ApplicationRoleLoader) {
                     return ApplicationRoleLoader();
+                },
+                roles : function(RoleListLoader) {
+                    return RoleListLoader();
+                },
+                applications : function(ApplicationListLoader) {
+                    return ApplicationListLoader();
                 }
             },
             controller : 'ApplicationRoleDetailCtrl'
diff --git a/admin-ui/src/main/resources/META-INF/resources/admin/js/controllers/applications.js b/admin-ui/src/main/resources/META-INF/resources/admin/js/controllers/applications.js
index df955ad..20d5962 100755
--- a/admin-ui/src/main/resources/META-INF/resources/admin/js/controllers/applications.js
+++ b/admin-ui/src/main/resources/META-INF/resources/admin/js/controllers/applications.js
@@ -99,7 +99,9 @@ module.controller('ApplicationSessionsCtrl', function($scope, $location, realm, 
     $scope.application = application;
 });
 
-module.controller('ApplicationRoleDetailCtrl', function($scope, realm, application, role, ApplicationRole, $location, Dialog, Notifications) {
+module.controller('ApplicationRoleDetailCtrl', function($scope, realm, application, role, roles, applications,
+                                                        Role, ApplicationRole, RoleById, RoleRealmComposites, RoleApplicationComposites,
+                                                        $http, $location, Dialog, Notifications) {
     $scope.realm = realm;
     $scope.application = application;
     $scope.role = angular.copy(role);
@@ -107,18 +109,6 @@ module.controller('ApplicationRoleDetailCtrl', function($scope, realm, applicati
 
     $scope.changed = $scope.create;
 
-    $scope.$watch(function() {
-        return $location.path();
-    }, function() {
-        $scope.path = $location.path().substring(1).split("/");
-    });
-
-    $scope.$watch('role', function() {
-        if (!angular.equals($scope.role, role)) {
-            $scope.changed = true;
-        }
-    }, true);
-
     $scope.save = function() {
         if ($scope.create) {
             ApplicationRole.save({
@@ -134,27 +124,10 @@ module.controller('ApplicationRoleDetailCtrl', function($scope, realm, applicati
                 Notifications.success("The role has been created.");
             });
         } else {
-            ApplicationRole.update({
-                realm : realm.realm,
-                application : application.name,
-                role : role.name
-            }, $scope.role, function() {
-                $scope.changed = false;
-                role = angular.copy($scope.role);
-                Notifications.success("Your changes have been saved to the role.");
-            });
+            $scope.update();
         }
     };
 
-    $scope.reset = function() {
-        $scope.role = angular.copy(role);
-        $scope.changed = false;
-    };
-
-    $scope.cancel = function() {
-        $location.url("/realms/" + realm.realm + "/applications/" + application.name + "/roles");
-    };
-
     $scope.remove = function() {
         Dialog.confirmDelete($scope.role.name, 'role', function() {
             $scope.role.$remove({
@@ -167,6 +140,16 @@ module.controller('ApplicationRoleDetailCtrl', function($scope, realm, applicati
             });
         });
     };
+
+    $scope.cancel = function () {
+        $location.url("/realms/" + realm.realm + "/applications/" + application.name + "/roles");
+    };
+
+
+    roleControl($scope, realm, role, roles, applications,
+        ApplicationRole, RoleById, RoleRealmComposites, RoleApplicationComposites,
+        $http, $location, Notifications, Dialog);
+
 });
 
 module.controller('ApplicationListCtrl', function($scope, realm, applications, Application, $location) {
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 3371766..3ff8682 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
@@ -675,26 +675,18 @@ module.controller('RoleListCtrl', function($scope, $location, realm, roles) {
     });
 });
 
-module.controller('RoleDetailCtrl', function($scope, realm, role, Role, $location, Dialog, Notifications) {
+
+module.controller('RoleDetailCtrl', function($scope, realm, role, roles, applications,
+                                             Role, ApplicationRole, RoleById, RoleRealmComposites, RoleApplicationComposites,
+                                             $http, $location, Dialog, Notifications) {
     $scope.realm = realm;
     $scope.role = angular.copy(role);
     $scope.create = !role.name;
 
     $scope.changed = $scope.create;
 
-    $scope.$watch(function() {
-        return $location.path();
-    }, function() {
-        $scope.path = $location.path().substring(1).split("/");
-    });
-
-    $scope.$watch('role', function() {
-        if (!angular.equals($scope.role, role)) {
-            $scope.changed = true;
-        }
-    }, true);
-
     $scope.save = function() {
+        console.log('save');
         if ($scope.create) {
             Role.save({
                 realm: realm.realm
@@ -708,37 +700,31 @@ module.controller('RoleDetailCtrl', function($scope, realm, role, Role, $locatio
                 Notifications.success("The role has been created.");
             });
         } else {
-            Role.update({
-                realm : realm.realm,
-                role : role.name
-            }, $scope.role, function() {
-                $scope.changed = false;
-                role = angular.copy($scope.role);
-                Notifications.success("Your changes have been saved to the role.");
-            });
+            $scope.update();
         }
     };
 
-    $scope.reset = function() {
-        $scope.role = angular.copy(role);
-        $scope.changed = false;
-    };
-
-    $scope.cancel = function() {
-        $location.url("/realms/" + realm.realm + "/roles");
-    };
-
-    $scope.remove = function() {
-        Dialog.confirmDelete($scope.role.name, 'role', function() {
+    $scope.remove = function () {
+        Dialog.confirmDelete($scope.role.name, 'role', function () {
             $scope.role.$remove({
-                realm : realm.realm,
-                role : $scope.role.name
-            }, function() {
+                realm: realm.realm,
+                role: $scope.role.name
+            }, function () {
                 $location.url("/realms/" + realm.realm + "/roles");
                 Notifications.success("The role has been deleted.");
             });
         });
     };
+
+    $scope.cancel = function () {
+        $location.url("/realms/" + realm.realm + "/roles");
+    };
+
+
+
+    roleControl($scope, realm, role, roles, applications,
+        ApplicationRole, RoleById, RoleRealmComposites, RoleApplicationComposites,
+        $http, $location, Notifications, Dialog);
 });
 
 module.controller('RealmSMTPSettingsCtrl', function($scope, Current, Realm, realm, $http, $location, Dialog, Notifications) {
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 193a8d3..33e47a9 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
@@ -1,453 +1,645 @@
-'use strict';
-
-var module = angular.module('keycloak.services', [ 'ngResource' ]);
-
-module.service('Dialog', function($dialog) {
-	var dialog = {};
-
-	var escapeHtml = function(str) {
-		var div = document.createElement('div');
-		div.appendChild(document.createTextNode(str));
-		return div.innerHTML;
-	};
-
-	dialog.confirmDelete = function(name, type, success) {
-		var title = 'Delete ' + escapeHtml(type.charAt(0).toUpperCase() + type.slice(1));
-		var msg = '<span class="primary">Are you sure you want to permanently delete the ' + escapeHtml(type) + ' "' + escapeHtml(name) + '"?</span>' +
-            '<span>This action can\'t be undone.</span>';
-		var btns = [ {
-			result : 'cancel',
-			label : 'Cancel'
-		}, {
-			result : 'ok',
-			label : 'Delete',
-			cssClass : 'destructive'
-		} ];
-
-		$dialog.messageBox(title, msg, btns).open().then(function(result) {
-			if (result == "ok") {
-				success();
-			}
-		});
-	}
-
-    dialog.confirmGenerateKeys = function(name, type, success) {
-        var title = 'Generate new keys for realm';
-        var msg = '<span class="primary">Are you sure you want to permanently generate new keys for ' + name + '"?</span>' +
-            '<span>This action can\'t be undone.</span>';
-        var btns = [ {
-            result : 'cancel',
-            label : 'Cancel'
-        }, {
-            result : 'ok',
-            label : 'Generate new keys',
-            cssClass : 'destructive'
-        } ];
-
-        $dialog.messageBox(title, msg, btns).open().then(function(result) {
-            if (result == "ok") {
-                success();
-            }
-        });
-    }
-
-	return dialog
-});
-
-module.factory('Notifications', function($rootScope, $timeout) {
-	// time (in ms) the notifications are shown
-	var delay = 5000;
-
-	var notifications = {};
-
-	var scheduled = null;
-	var schedulePop = function() {
-		if (scheduled) {
-			$timeout.cancel(scheduled);
-		}
-
-		scheduled = $timeout(function() {
-			$rootScope.notification = null;
-			scheduled = null;
-		}, delay);
-	};
-
-	if (!$rootScope.notifications) {
-		$rootScope.notifications = [];
-	}
-
-	notifications.message = function(type, header, message) {
-		$rootScope.notification = {
-			type : type,
-			header: header,
-			message : message
-		};
-
-		schedulePop();
-	}
-
-	notifications.info = function(message) {
-		notifications.message("info", "Info!", message);
-	};
-
-	notifications.success = function(message) {
-		notifications.message("success", "Success!", message);
-	};
-
-	notifications.error = function(message) {
-		notifications.message("error", "Error!", message);
-	};
-
-	notifications.warn = function(message) {
-		notifications.message("warn", "Warning!", message);
-	};
-
-	return notifications;
-});
-
-module.factory('Realm', function($resource) {
-	return $resource('/auth/rest/admin/realms/:id', {
-		id : '@realm'
-	}, {
-		update : {
-			method : 'PUT'
-		},
-        create : {
-            method : 'POST',
-            params : { id : ''}
-        }
-
-    });
-});
-
-module.factory('User', function($resource) {
-	return $resource('/auth/rest/admin/realms/:realm/users/:userId', {
-		realm : '@realm',
-		userId : '@userId'
-	}, {
-        update : {
-            method : 'PUT'
-        }
-	});
-});
-
-module.factory('UserCredentials', function($resource) {
-    return $resource('/auth/rest/admin/realms/:realm/users/:userId/credentials', {
-        realm : '@realm',
-        userId : '@userId'
-    }, {
-        update : {
-            method : 'PUT',
-            isArray : true
-        }
-    });
-});
-
-module.factory('RealmRoleMapping', function($resource) {
-    return $resource('/auth/rest/admin/realms/:realm/users/:userId/role-mappings/realm', {
-        realm : '@realm',
-        userId : '@userId'
-    });
-});
-
-module.factory('ApplicationRoleMapping', function($resource) {
-    return $resource('/auth/rest/admin/realms/:realm/users/:userId/role-mappings/applications/:application', {
-        realm : '@realm',
-        userId : '@userId',
-        application : "@application"
-    });
-});
-
-module.factory('ApplicationRealmScopeMapping', function($resource) {
-    return $resource('/auth/rest/admin/realms/:realm/applications/:application/scope-mappings/realm', {
-        realm : '@realm',
-        application : '@application'
-    });
-});
-
-module.factory('ApplicationApplicationScopeMapping', function($resource) {
-    return $resource('/auth/rest/admin/realms/:realm/applications/:application/scope-mappings/applications/:targetApp', {
-        realm : '@realm',
-        application : '@application',
-        targetApp : '@targetApp'
-    });
-});
-
-
-
-module.factory('RealmRoles', function($resource) {
-    return $resource('/auth/rest/admin/realms/:realm/roles', {
-        realm : '@realm'
-    });
-});
-
-
-
-module.factory('Role', function($resource) {
-    return $resource('/auth/rest/admin/realms/:realm/roles/:role', {
-        realm : '@realm',
-        role : '@role'
-    },  {
-        update : {
-            method : 'PUT'
-        }
-    });
-});
-
-module.factory('ApplicationRole', function($resource) {
-    return $resource('/auth/rest/admin/realms/:realm/applications/:application/roles/:role', {
-        realm : '@realm',
-        application : "@application",
-        role : '@role'
-    },  {
-        update : {
-            method : 'PUT'
-        }
-    });
-});
-
-
-module.factory('Application', function($resource) {
-    return $resource('/auth/rest/admin/realms/:realm/applications/:application', {
-        realm : '@realm',
-        application : '@name'
-    },  {
-        update : {
-            method : 'PUT'
-        }
-    });
-});
-
-module.factory('ApplicationInstallation', function($resource) {
-    var url = '/auth/rest/admin/realms/:realm/applications/:application/installation';
-    var resource = $resource('/auth/rest/admin/realms/:realm/applications/:application/installation', {
-        realm : '@realm',
-        application : '@application'
-    },  {
-        update : {
-            method : 'PUT'
-        }
-    });
-    resource.url = function(parameters) {
-        return url.replace(':realm', parameters.realm).replace(':application', parameters.application);
-    }
-    return resource;
-});
-
-module.factory('ApplicationCredentials', function($resource) {
-    return $resource('/auth/rest/admin/realms/:realm/applications/:application/credentials', {
-        realm : '@realm',
-        application : '@application'
-    },  {
-        update : {
-            method : 'PUT',
-            isArray : true
-        }
-    });
-});
-
-module.factory('ApplicationOrigins', function($resource) {
-    return $resource('/auth/rest/admin/realms/:realm/applications/:application/allowed-origins', {
-        realm : '@realm',
-        application : '@application'
-    },  {
-        update : {
-            method : 'PUT',
-            isArray : true
-        }
-    });
-});
-
-module.factory('OAuthClient', function($resource) {
-    return $resource('/auth/rest/admin/realms/:realm/oauth-clients/:id', {
-        realm : '@realm',
-        id : '@id'
-    },  {
-        update : {
-            method : 'PUT'
-        }
-    });
-});
-
-module.factory('OAuthClientCredentials', function($resource) {
-    return $resource('/auth/rest/admin/realms/:realm/oauth-clients/:oauth/credentials', {
-        realm : '@realm',
-        oauth : '@oauth'
-    },  {
-        update : {
-            method : 'PUT',
-            isArray : true
-        }
-    });
-});
-
-module.factory('OAuthClientRealmScopeMapping', function($resource) {
-    return $resource('/auth/rest/admin/realms/:realm/oauth-clients/:oauth/scope-mappings/realm', {
-        realm : '@realm',
-        oauth : '@oauth'
-    });
-});
-
-module.factory('OAuthClientApplicationScopeMapping', function($resource) {
-    return $resource('/auth/rest/admin/realms/:realm/oauth-clients/:oauth/scope-mappings/applications/:targetApp', {
-        realm : '@realm',
-        oauth : '@oauth',
-        targetApp : '@targetApp'
-    });
-});
-
-module.factory('OAuthClientInstallation', function($resource) {
-    var url = '/auth/rest/admin/realms/:realm/oauth-clients/:oauth/installation';
-    var resource = $resource('/auth/rest/admin/realms/:realm/oauth-clients/:oauth/installation', {
-        realm : '@realm',
-        oauth : '@oauth'
-    },  {
-        update : {
-            method : 'PUT'
-        }
-    });
-    resource.url = function(parameters) {
-        return url.replace(':realm', parameters.realm).replace(':oauth', parameters.oauth);
-    }
-    return resource;
-});
-
-
-module.factory('Current', function(Realm, $route) {
-    var current = {};
-
-    current.realms = {};
-    current.realm = null;
-    current.applications = {};
-    current.application = null;
-
-    current.refresh = function() {
-        current.realm = null;
-        current.realms = Realm.query(null, function(realms) {
-            if ($route.current.params.realm) {
-                for (var i = 0; i < realms.length; i++) {
-                    if (realms[i].realm == $route.current.params.realm) {
-                        current.realm =  realms[i];
-                    }
-                }
-            }
-        });
-    }
-
-    current.refresh();
-
-    return current;
-});
-
-module.factory('TimeUnit', function() {
-    var t = {};
-
-    t.autoUnit = function(time) {
-        var unit = 'Seconds';
-        if (time % 60 == 0) {
-            unit = 'Minutes';
-            time  = time / 60;
-        }
-        if (time % 60 == 0) {
-            unit = 'Hours';
-            time = time / 60;
-        }
-        if (time % 24 == 0) {
-            unit = 'Days'
-            time = time / 24;
-        }
-        return unit;
-    }
-
-    t.toSeconds = function(time, unit) {
-        switch (unit) {
-            case 'Seconds': return time;
-            case 'Minutes': return time * 60;
-            case 'Hours': return time * 360;
-            case 'Days': return time * 86400;
-            default: throw 'invalid unit ' + unit;
-        }
-    }
-
-    t.toUnit = function(time, unit) {
-        switch (unit) {
-            case 'Seconds': return time;
-            case 'Minutes': return Math.ceil(time / 60);
-            case 'Hours': return Math.ceil(time / 360);
-            case 'Days': return Math.ceil(time / 86400);
-            default: throw 'invalid unit ' + unit;
-        }
-    }
-
-    t.convert = function(time, from, to) {
-        var seconds = t.toSeconds(time, from);
-        return t.toUnit(seconds, to);
-    }
-
-    return t;
-});
-
-
-module.factory('PasswordPolicy', function() {
-    var p = {};
-
-    p.policyMessages = {
-        length:         "Minimal password length (integer type). Default value is 8.",
-        digits:         "Minimal number (integer type) of digits in password. Default value is 1.",
-        lowerCase:      "Minimal number (integer type) of lowercase characters in password. Default value is 1.",
-        upperCase:      "Minimal number (integer type) of uppercase characters in password. Default value is 1.",
-        specialChars:   "Minimal number (integer type) of special characters in password. Default value is 1."
-    }
-
-    p.allPolicies = [
-        { name: 'length', value: 8 },
-        { name: 'digits', value: 1 },
-        { name: 'lowerCase', value: 1 },
-        { name: 'upperCase', value: 1 },
-        { name: 'specialChars', value: 1 }
-    ];
-
-    p.parse = function(policyString) {
-        var policies = [];
-
-        if (!policyString || policyString.length == 0){
-            return policies;
-        }
-
-        var policyArray = policyString.split(" and ");
-
-        for (var i = 0; i < policyArray.length; i ++){
-            var policyToken = policyArray[i];
-            var re = /(\w+)\(*(\d*)\)*/;
-
-            var policyEntry = re.exec(policyToken);
-
-            policies.push({ name: policyEntry[1], value: parseInt(policyEntry[2]) });
-
-        }
-
-        return policies;
-    };
-
-    p.toString = function(policies) {
-        if (!policies || policies.length == 0) {
-            return null;
-        }
-
-        var policyString = "";
-
-        for (var i in policies){
-            policyString += policies[i].name;
-            if ( policies[i].value ){
-                policyString += '(' + policies[i].value + ')';
-            }
-            policyString += " and ";
-        }
-
-        policyString = policyString.substring(0, policyString.length - 5);
-
-        return policyString;
-    };
-
-    return p;
+'use strict';
+
+var module = angular.module('keycloak.services', [ 'ngResource' ]);
+
+module.service('Dialog', function($dialog) {
+	var dialog = {};
+
+	var escapeHtml = function(str) {
+		var div = document.createElement('div');
+		div.appendChild(document.createTextNode(str));
+		return div.innerHTML;
+	};
+
+	dialog.confirmDelete = function(name, type, success) {
+		var title = 'Delete ' + escapeHtml(type.charAt(0).toUpperCase() + type.slice(1));
+		var msg = '<span class="primary">Are you sure you want to permanently delete the ' + escapeHtml(type) + ' "' + escapeHtml(name) + '"?</span>' +
+            '<span>This action can\'t be undone.</span>';
+		var btns = [ {
+			result : 'cancel',
+			label : 'Cancel'
+		}, {
+			result : 'ok',
+			label : 'Delete',
+			cssClass : 'destructive'
+		} ];
+
+		$dialog.messageBox(title, msg, btns).open().then(function(result) {
+			if (result == "ok") {
+				success();
+			}
+		});
+	}
+
+    dialog.confirmGenerateKeys = function(name, type, success) {
+        var title = 'Generate new keys for realm';
+        var msg = '<span class="primary">Are you sure you want to permanently generate new keys for ' + name + '"?</span>' +
+            '<span>This action can\'t be undone.</span>';
+        var btns = [ {
+            result : 'cancel',
+            label : 'Cancel'
+        }, {
+            result : 'ok',
+            label : 'Generate new keys',
+            cssClass : 'destructive'
+        } ];
+
+        $dialog.messageBox(title, msg, btns).open().then(function(result) {
+            if (result == "ok") {
+                success();
+            }
+        });
+    }
+
+	return dialog
+});
+
+module.factory('Notifications', function($rootScope, $timeout) {
+	// time (in ms) the notifications are shown
+	var delay = 5000;
+
+	var notifications = {};
+
+	var scheduled = null;
+	var schedulePop = function() {
+		if (scheduled) {
+			$timeout.cancel(scheduled);
+		}
+
+		scheduled = $timeout(function() {
+			$rootScope.notification = null;
+			scheduled = null;
+		}, delay);
+	};
+
+	if (!$rootScope.notifications) {
+		$rootScope.notifications = [];
+	}
+
+	notifications.message = function(type, header, message) {
+		$rootScope.notification = {
+			type : type,
+			header: header,
+			message : message
+		};
+
+		schedulePop();
+	}
+
+	notifications.info = function(message) {
+		notifications.message("info", "Info!", message);
+	};
+
+	notifications.success = function(message) {
+		notifications.message("success", "Success!", message);
+	};
+
+	notifications.error = function(message) {
+		notifications.message("error", "Error!", message);
+	};
+
+	notifications.warn = function(message) {
+		notifications.message("warn", "Warning!", message);
+	};
+
+	return notifications;
+});
+
+module.factory('Realm', function($resource) {
+	return $resource('/auth/rest/admin/realms/:id', {
+		id : '@realm'
+	}, {
+		update : {
+			method : 'PUT'
+		},
+        create : {
+            method : 'POST',
+            params : { id : ''}
+        }
+
+    });
+});
+
+module.factory('User', function($resource) {
+	return $resource('/auth/rest/admin/realms/:realm/users/:userId', {
+		realm : '@realm',
+		userId : '@userId'
+	}, {
+        update : {
+            method : 'PUT'
+        }
+	});
+});
+
+module.factory('UserCredentials', function($resource) {
+    return $resource('/auth/rest/admin/realms/:realm/users/:userId/credentials', {
+        realm : '@realm',
+        userId : '@userId'
+    }, {
+        update : {
+            method : 'PUT',
+            isArray : true
+        }
+    });
+});
+
+module.factory('RealmRoleMapping', function($resource) {
+    return $resource('/auth/rest/admin/realms/:realm/users/:userId/role-mappings/realm', {
+        realm : '@realm',
+        userId : '@userId'
+    });
+});
+
+module.factory('ApplicationRoleMapping', function($resource) {
+    return $resource('/auth/rest/admin/realms/:realm/users/:userId/role-mappings/applications/:application', {
+        realm : '@realm',
+        userId : '@userId',
+        application : "@application"
+    });
+});
+
+module.factory('ApplicationRealmScopeMapping', function($resource) {
+    return $resource('/auth/rest/admin/realms/:realm/applications/:application/scope-mappings/realm', {
+        realm : '@realm',
+        application : '@application'
+    });
+});
+
+module.factory('ApplicationApplicationScopeMapping', function($resource) {
+    return $resource('/auth/rest/admin/realms/:realm/applications/:application/scope-mappings/applications/:targetApp', {
+        realm : '@realm',
+        application : '@application',
+        targetApp : '@targetApp'
+    });
+});
+
+
+
+module.factory('RealmRoles', function($resource) {
+    return $resource('/auth/rest/admin/realms/:realm/roles', {
+        realm : '@realm'
+    });
+});
+
+module.factory('RoleRealmComposites', function($resource) {
+    return $resource('/auth/rest/admin/realms/:realm/roles-by-id/:role/composites/realm', {
+        realm : '@realm',
+        role : '@role'
+    });
+});
+
+module.factory('RoleApplicationComposites', function($resource) {
+    return $resource('/auth/rest/admin/realms/:realm/roles-by-id/:role/composites/applications/:application', {
+        realm : '@realm',
+        role : '@role',
+        application : "@application"
+    });
+});
+
+
+function roleControl($scope, realm, role, roles, applications,
+                     ApplicationRole, RoleById, RoleRealmComposites, RoleApplicationComposites,
+                     $http, $location, Notifications, Dialog) {
+
+    $scope.$watch(function () {
+        return $location.path();
+    }, function () {
+        $scope.path = $location.path().substring(1).split("/");
+    });
+
+    $scope.$watch('role', function () {
+        if (!angular.equals($scope.role, role)) {
+            $scope.changed = true;
+        }
+    }, true);
+
+    $scope.update = function () {
+        RoleById.update({
+            realm: realm.realm,
+            role: role.id
+        }, $scope.role, function () {
+            $scope.changed = false;
+            role = angular.copy($scope.role);
+            Notifications.success("Your changes have been saved to the role.");
+        });
+    };
+
+    $scope.reset = function () {
+        $scope.role = angular.copy(role);
+        $scope.changed = false;
+    };
+
+    if (!role.id) return;
+
+    $scope.realmRoles = angular.copy(roles);
+    $scope.selectedRealmRoles = [];
+    $scope.selectedRealmMappings = [];
+    $scope.realmMappings = [];
+    $scope.applications = applications;
+    $scope.applicationRoles = [];
+    $scope.selectedApplicationRoles = [];
+    $scope.selectedApplicationMappings = [];
+    $scope.applicationMappings = [];
+
+    console.log('remove self');
+    for (var j = 0; j < $scope.realmRoles.length; j++) {
+        if ($scope.realmRoles[j].id == role.id) {
+            var realmRole = $scope.realmRoles[j];
+            var idx = $scope.realmRoles.indexOf(realmRole);
+            $scope.realmRoles.splice(idx, 1);
+            break;
+        }
+    }
+
+
+    $scope.realmMappings = RoleRealmComposites.query({realm : realm.realm, role : role.id}, function(){
+        for (var i = 0; i < $scope.realmMappings.length; i++) {
+            var role = $scope.realmMappings[i];
+            for (var j = 0; j < $scope.realmRoles.length; j++) {
+                var realmRole = $scope.realmRoles[j];
+                if (realmRole.id == role.id) {
+                    var idx = $scope.realmRoles.indexOf(realmRole);
+                    if (idx != -1) {
+                        $scope.realmRoles.splice(idx, 1);
+                        break;
+                    }
+                }
+            }
+        }
+    });
+
+    $scope.addRealmRole = function() {
+        $http.post('/auth/rest/admin/realms/' + realm.realm + '/roles-by-id/' + role.id + '/composites',
+                $scope.selectedRealmRoles).success(function() {
+                for (var i = 0; i < $scope.selectedRealmRoles.length; i++) {
+                    var role = $scope.selectedRealmRoles[i];
+                    var idx = $scope.realmRoles.indexOf($scope.selectedRealmRoles[i]);
+                    if (idx != -1) {
+                        $scope.realmRoles.splice(idx, 1);
+                        $scope.realmMappings.push(role);
+                    }
+                }
+                $scope.selectRealmRoles = [];
+            });
+    };
+
+    $scope.deleteRealmRole = function() {
+        $http.delete('/auth/rest/admin/realms/' + realm.realm + '/roles-by-id/' + role.id + '/composites',
+            {data : $scope.selectedRealmMappings, headers : {"content-type" : "application/json"}}).success(function() {
+                for (var i = 0; i < $scope.selectedRealmMappings.length; i++) {
+                    var role = $scope.selectedRealmMappings[i];
+                    var idx = $scope.realmMappings.indexOf($scope.selectedRealmMappings[i]);
+                    if (idx != -1) {
+                        $scope.realmMappings.splice(idx, 1);
+                        $scope.realmRoles.push(role);
+                    }
+                }
+                $scope.selectedRealmMappings = [];
+            });
+    };
+
+    $scope.addApplicationRole = function() {
+        $http.post('/auth/rest/admin/realms/' + realm.realm + '/roles-by-id/' + role.id + '/composites',
+                $scope.selectedApplicationRoles).success(function() {
+                for (var i = 0; i < $scope.selectedApplicationRoles.length; i++) {
+                    var role = $scope.selectedApplicationRoles[i];
+                    var idx = $scope.applicationRoles.indexOf($scope.selectedApplicationRoles[i]);
+                    if (idx != -1) {
+                        $scope.applicationRoles.splice(idx, 1);
+                        $scope.applicationMappings.push(role);
+                    }
+                }
+                $scope.selectedApplicationRoles = [];
+            });
+    };
+
+    $scope.deleteApplicationRole = function() {
+        $http.delete('/auth/rest/admin/realms/' + realm.realm + '/roles-by-id/' + role.id + '/composites',
+            {data : $scope.selectedApplicationMappings, headers : {"content-type" : "application/json"}}).success(function() {
+                for (var i = 0; i < $scope.selectedApplicationMappings.length; i++) {
+                    var role = $scope.selectedApplicationMappings[i];
+                    var idx = $scope.applicationMappings.indexOf($scope.selectedApplicationMappings[i]);
+                    if (idx != -1) {
+                        $scope.applicationMappings.splice(idx, 1);
+                        $scope.applicationRoles.push(role);
+                    }
+                }
+                $scope.selectedApplicationMappings = [];
+            });
+    };
+
+
+    $scope.changeApplication = function() {
+        $scope.applicationRoles = ApplicationRole.query({realm : realm.realm, application : $scope.compositeApp.name}, function() {
+                $scope.applicationMappings = RoleApplicationComposites.query({realm : realm.realm, role : role.id, application : $scope.compositeApp.name}, function(){
+                    for (var i = 0; i < $scope.applicationMappings.length; i++) {
+                        var role = $scope.applicationMappings[i];
+                        for (var j = 0; j < $scope.applicationRoles.length; j++) {
+                            var realmRole = $scope.applicationRoles[j];
+                            if (realmRole.id == role.id) {
+                                var idx = $scope.applicationRoles.indexOf(realmRole);
+                                if (idx != -1) {
+                                    $scope.applicationRoles.splice(idx, 1);
+                                    break;
+                                }
+                            }
+                        }
+                    }
+                });
+                for (var j = 0; j < $scope.applicationRoles.length; j++) {
+                    if ($scope.applicationRoles[j] == role.id) {
+                        var appRole = $scope.applicationRoles[j];
+                        var idx = $scope.applicationRoles.indexof(appRole);
+                        $scope.applicationRoles.splice(idx, 1);
+                        break;
+                    }
+                }
+            }
+        );
+    };
+
+
+
+
+}
+
+
+module.factory('Role', function($resource) {
+    return $resource('/auth/rest/admin/realms/:realm/roles/:role', {
+        realm : '@realm',
+        role : '@role'
+    },  {
+        update : {
+            method : 'PUT'
+        }
+    });
+});
+
+module.factory('RoleById', function($resource) {
+    return $resource('/auth/rest/admin/realms/:realm/roles-by-id/:role', {
+        realm : '@realm',
+        role : '@role'
+    },  {
+        update : {
+            method : 'PUT'
+        }
+    });
+});
+
+module.factory('ApplicationRole', function($resource) {
+    return $resource('/auth/rest/admin/realms/:realm/applications/:application/roles/:role', {
+        realm : '@realm',
+        application : "@application",
+        role : '@role'
+    },  {
+        update : {
+            method : 'PUT'
+        }
+    });
+});
+
+
+module.factory('Application', function($resource) {
+    return $resource('/auth/rest/admin/realms/:realm/applications/:application', {
+        realm : '@realm',
+        application : '@name'
+    },  {
+        update : {
+            method : 'PUT'
+        }
+    });
+});
+
+module.factory('ApplicationInstallation', function($resource) {
+    var url = '/auth/rest/admin/realms/:realm/applications/:application/installation';
+    var resource = $resource('/auth/rest/admin/realms/:realm/applications/:application/installation', {
+        realm : '@realm',
+        application : '@application'
+    },  {
+        update : {
+            method : 'PUT'
+        }
+    });
+    resource.url = function(parameters) {
+        return url.replace(':realm', parameters.realm).replace(':application', parameters.application);
+    }
+    return resource;
+});
+
+module.factory('ApplicationCredentials', function($resource) {
+    return $resource('/auth/rest/admin/realms/:realm/applications/:application/credentials', {
+        realm : '@realm',
+        application : '@application'
+    },  {
+        update : {
+            method : 'PUT',
+            isArray : true
+        }
+    });
+});
+
+module.factory('ApplicationOrigins', function($resource) {
+    return $resource('/auth/rest/admin/realms/:realm/applications/:application/allowed-origins', {
+        realm : '@realm',
+        application : '@application'
+    },  {
+        update : {
+            method : 'PUT',
+            isArray : true
+        }
+    });
+});
+
+module.factory('OAuthClient', function($resource) {
+    return $resource('/auth/rest/admin/realms/:realm/oauth-clients/:id', {
+        realm : '@realm',
+        id : '@id'
+    },  {
+        update : {
+            method : 'PUT'
+        }
+    });
+});
+
+module.factory('OAuthClientCredentials', function($resource) {
+    return $resource('/auth/rest/admin/realms/:realm/oauth-clients/:oauth/credentials', {
+        realm : '@realm',
+        oauth : '@oauth'
+    },  {
+        update : {
+            method : 'PUT',
+            isArray : true
+        }
+    });
+});
+
+module.factory('OAuthClientRealmScopeMapping', function($resource) {
+    return $resource('/auth/rest/admin/realms/:realm/oauth-clients/:oauth/scope-mappings/realm', {
+        realm : '@realm',
+        oauth : '@oauth'
+    });
+});
+
+module.factory('OAuthClientApplicationScopeMapping', function($resource) {
+    return $resource('/auth/rest/admin/realms/:realm/oauth-clients/:oauth/scope-mappings/applications/:targetApp', {
+        realm : '@realm',
+        oauth : '@oauth',
+        targetApp : '@targetApp'
+    });
+});
+
+module.factory('OAuthClientInstallation', function($resource) {
+    var url = '/auth/rest/admin/realms/:realm/oauth-clients/:oauth/installation';
+    var resource = $resource('/auth/rest/admin/realms/:realm/oauth-clients/:oauth/installation', {
+        realm : '@realm',
+        oauth : '@oauth'
+    },  {
+        update : {
+            method : 'PUT'
+        }
+    });
+    resource.url = function(parameters) {
+        return url.replace(':realm', parameters.realm).replace(':oauth', parameters.oauth);
+    }
+    return resource;
+});
+
+
+module.factory('Current', function(Realm, $route) {
+    var current = {};
+
+    current.realms = {};
+    current.realm = null;
+    current.applications = {};
+    current.application = null;
+
+    current.refresh = function() {
+        current.realm = null;
+        current.realms = Realm.query(null, function(realms) {
+            if ($route.current.params.realm) {
+                for (var i = 0; i < realms.length; i++) {
+                    if (realms[i].realm == $route.current.params.realm) {
+                        current.realm =  realms[i];
+                    }
+                }
+            }
+        });
+    }
+
+    current.refresh();
+
+    return current;
+});
+
+module.factory('TimeUnit', function() {
+    var t = {};
+
+    t.autoUnit = function(time) {
+        var unit = 'Seconds';
+        if (time % 60 == 0) {
+            unit = 'Minutes';
+            time  = time / 60;
+        }
+        if (time % 60 == 0) {
+            unit = 'Hours';
+            time = time / 60;
+        }
+        if (time % 24 == 0) {
+            unit = 'Days'
+            time = time / 24;
+        }
+        return unit;
+    }
+
+    t.toSeconds = function(time, unit) {
+        switch (unit) {
+            case 'Seconds': return time;
+            case 'Minutes': return time * 60;
+            case 'Hours': return time * 360;
+            case 'Days': return time * 86400;
+            default: throw 'invalid unit ' + unit;
+        }
+    }
+
+    t.toUnit = function(time, unit) {
+        switch (unit) {
+            case 'Seconds': return time;
+            case 'Minutes': return Math.ceil(time / 60);
+            case 'Hours': return Math.ceil(time / 360);
+            case 'Days': return Math.ceil(time / 86400);
+            default: throw 'invalid unit ' + unit;
+        }
+    }
+
+    t.convert = function(time, from, to) {
+        var seconds = t.toSeconds(time, from);
+        return t.toUnit(seconds, to);
+    }
+
+    return t;
+});
+
+
+module.factory('PasswordPolicy', function() {
+    var p = {};
+
+    p.policyMessages = {
+        length:         "Minimal password length (integer type). Default value is 8.",
+        digits:         "Minimal number (integer type) of digits in password. Default value is 1.",
+        lowerCase:      "Minimal number (integer type) of lowercase characters in password. Default value is 1.",
+        upperCase:      "Minimal number (integer type) of uppercase characters in password. Default value is 1.",
+        specialChars:   "Minimal number (integer type) of special characters in password. Default value is 1."
+    }
+
+    p.allPolicies = [
+        { name: 'length', value: 8 },
+        { name: 'digits', value: 1 },
+        { name: 'lowerCase', value: 1 },
+        { name: 'upperCase', value: 1 },
+        { name: 'specialChars', value: 1 }
+    ];
+
+    p.parse = function(policyString) {
+        var policies = [];
+
+        if (!policyString || policyString.length == 0){
+            return policies;
+        }
+
+        var policyArray = policyString.split(" and ");
+
+        for (var i = 0; i < policyArray.length; i ++){
+            var policyToken = policyArray[i];
+            var re = /(\w+)\(*(\d*)\)*/;
+
+            var policyEntry = re.exec(policyToken);
+
+            policies.push({ name: policyEntry[1], value: parseInt(policyEntry[2]) });
+
+        }
+
+        return policies;
+    };
+
+    p.toString = function(policies) {
+        if (!policies || policies.length == 0) {
+            return null;
+        }
+
+        var policyString = "";
+
+        for (var i in policies){
+            policyString += policies[i].name;
+            if ( policies[i].value ){
+                policyString += '(' + policies[i].value + ')';
+            }
+            policyString += " and ";
+        }
+
+        policyString = policyString.substring(0, policyString.length - 5);
+
+        return policyString;
+    };
+
+    return p;
 });
\ No newline at end of file
diff --git a/admin-ui/src/main/resources/META-INF/resources/admin/partials/application-role-detail.html b/admin-ui/src/main/resources/META-INF/resources/admin/partials/application-role-detail.html
index 84a39e3..4c6b5b2 100755
--- a/admin-ui/src/main/resources/META-INF/resources/admin/partials/application-role-detail.html
+++ b/admin-ui/src/main/resources/META-INF/resources/admin/partials/application-role-detail.html
@@ -52,6 +52,10 @@
                                        required> -->
                             </div>
                         </div>
+                        <div class="form-group clearfix block">
+                            <label for="composite" class="control-label">Composite</label>
+                            <input ng-model="role.composite" name="composite" id="composite" onoffswitch />
+                        </div>
                     </fieldset>
                     <div class="form-actions" data-ng-show="create">
                         <button type="submit" kc-save class="primary" data-ng-show="changed">Save
@@ -70,6 +74,72 @@
                             Delete
                         </button>
                     </div>
+                    <fieldset data-ng-show="!create && role.composite">
+                        <legend collapsed><span class="text">Composite Realm Roles</span> </legend>
+                        <div class="form-group">
+                            <div class="controls changing-selectors">
+                                <div class="select-title">
+                                    <label for="available">Available Roles</label>
+                                    <select id="available" class="form-control" multiple size="5"
+                                            ng-multiple="true"
+                                            ng-model="selectedRealmRoles"
+                                            ng-options="r.name for r in realmRoles">
+                                    </select>
+                                </div>
+                                <div class="middle-buttons">
+                                    <button type="submit" ng-click="addRealmRole()" tooltip="Move right" tooltip-placement="right"><span class="icon-arrow-right">Move right</span></button>
+                                    <button type="submit" ng-click="deleteRealmRole()" tooltip="Move left" tooltip-placement="right"><span class="icon-arrow-left">Move left</span></button>
+                                </div>
+                                <div class="select-title">
+                                    <label for="assigned">Assigned Roles</label>
+                                    <select id="assigned" class="form-control" multiple size=5
+                                            ng-multiple="true"
+                                            ng-model="selectedRealmMappings"
+                                            ng-options="r.name for r in realmMappings">
+                                    </select>
+                                </div>
+                            </div>
+                        </div>
+                    </fieldset>
+
+                    <fieldset ng-show="applications.length > 0 && !create && role.composite">
+                        <legend collapsed><span class="text">Composite Application Roles</span> </legend>
+                        <div class="form-group input-select">
+                            <label for="applications">Application</label>
+                            <div class="input-group">
+                                <div class="select-rcue">
+                                    <select id="applications" name="applications" ng-change="changeApplication()" ng-model="compositeApp" ng-options="a.name for a in applications">
+                                        <option value="" selected> Select an Application...</option>
+                                    </select>
+                                </div>
+                            </div>
+                        </div>
+                        <div class="form-group" ng-show="compositeApp">
+                            <div class="controls changing-selectors application">
+                                <div class="select-title">
+                                    <label for="available-app">Available Roles</label>
+                                    <select id="available-app" class="form-control" multiple size="5"
+                                            ng-multiple="true"
+                                            ng-model="selectedApplicationRoles"
+                                            ng-options="r.name for r in applicationRoles">
+                                    </select>
+                                </div>
+                                <div class="middle-buttons">
+                                    <button type="submit" ng-click="addApplicationRole()" tooltip="Move right" tooltip-placement="right"><span class="icon-arrow-right">Move right</span></button>
+                                    <button type="submit" ng-click="deleteApplicationRole()" tooltip="Move left" tooltip-placement="right"><span class="icon-arrow-left">Move left</span></button>
+                                </div>
+                                <div class="select-title">
+                                    <label for="assigned-app">Assigned Roles</label>
+                                    <select id="assigned-app" class="form-control" multiple size=5
+                                            ng-multiple="true"
+                                            ng-model="selectedApplicationMappings"
+                                            ng-options="r.name for r in applicationMappings">
+                                    </select>
+                                </div>
+                            </div>
+                        </div>
+                    </fieldset>
+
 
                 </form>
             </div>
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 50cf512..b835fcb 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
@@ -47,6 +47,10 @@
                                 <textarea rows="5" cols="50" id="description" name="description" data-ng-model="role.description"></textarea>
                             </div>
                         </div>
+                        <div class="form-group clearfix block">
+                            <label for="composite" class="control-label">Composite</label>
+                            <input ng-model="role.composite" name="composite" id="composite" onoffswitch />
+                        </div>
                     </fieldset>
                     <div class="form-actions" data-ng-show="create">
                         <button type="submit" kc-save class="primary" data-ng-show="changed">Save
@@ -66,6 +70,72 @@
                         </button>
                     </div>
 
+                    <fieldset data-ng-show="!create && role.composite">
+                        <legend collapsed><span class="text">Composite Realm Roles</span> </legend>
+                        <div class="form-group">
+                            <div class="controls changing-selectors">
+                                <div class="select-title">
+                                    <label for="available">Available Roles</label>
+                                    <select id="available" class="form-control" multiple size="5"
+                                            ng-multiple="true"
+                                            ng-model="selectedRealmRoles"
+                                            ng-options="r.name for r in realmRoles">
+                                    </select>
+                                </div>
+                                <div class="middle-buttons">
+                                    <button type="submit" ng-click="addRealmRole()" tooltip="Move right" tooltip-placement="right"><span class="icon-arrow-right">Move right</span></button>
+                                    <button type="submit" ng-click="deleteRealmRole()" tooltip="Move left" tooltip-placement="right"><span class="icon-arrow-left">Move left</span></button>
+                                </div>
+                                <div class="select-title">
+                                    <label for="assigned">Assigned Roles</label>
+                                    <select id="assigned" class="form-control" multiple size=5
+                                            ng-multiple="true"
+                                            ng-model="selectedRealmMappings"
+                                            ng-options="r.name for r in realmMappings">
+                                    </select>
+                                </div>
+                            </div>
+                        </div>
+                    </fieldset>
+
+                    <fieldset ng-show="applications.length > 0 && !create && role.composite">
+                        <legend collapsed><span class="text">Composite Application Roles</span> </legend>
+                        <div class="form-group input-select">
+                            <label for="applications">Application</label>
+                            <div class="input-group">
+                                <div class="select-rcue">
+                                    <select id="applications" name="applications" ng-change="changeApplication()" ng-model="compositeApp" ng-options="a.name for a in applications">
+                                        <option value="" selected> Select an Application...</option>
+                                    </select>
+                                </div>
+                            </div>
+                        </div>
+                        <div class="form-group" ng-show="compositeApp">
+                            <div class="controls changing-selectors application">
+                                <div class="select-title">
+                                    <label for="available-app">Available Roles</label>
+                                    <select id="available-app" class="form-control" multiple size="5"
+                                            ng-multiple="true"
+                                            ng-model="selectedApplicationRoles"
+                                            ng-options="r.name for r in applicationRoles">
+                                    </select>
+                                </div>
+                                <div class="middle-buttons">
+                                    <button type="submit" ng-click="addApplicationRole()" tooltip="Move right" tooltip-placement="right"><span class="icon-arrow-right">Move right</span></button>
+                                    <button type="submit" ng-click="deleteApplicationRole()" tooltip="Move left" tooltip-placement="right"><span class="icon-arrow-left">Move left</span></button>
+                                </div>
+                                <div class="select-title">
+                                    <label for="assigned-app">Assigned Roles</label>
+                                    <select id="assigned-app" class="form-control" multiple size=5
+                                            ng-multiple="true"
+                                            ng-model="selectedApplicationMappings"
+                                            ng-options="r.name for r in applicationMappings">
+                                    </select>
+                                </div>
+                            </div>
+                        </div>
+                    </fieldset>
+
                 </form>
             </div>
         </div>
diff --git a/core/src/main/java/org/keycloak/representations/idm/RoleRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/RoleRepresentation.java
index 59119a6..81d2428 100755
--- a/core/src/main/java/org/keycloak/representations/idm/RoleRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/RoleRepresentation.java
@@ -1,5 +1,8 @@
 package org.keycloak.representations.idm;
 
+import java.util.List;
+import java.util.Set;
+
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
  * @version $Revision: 1 $
@@ -9,6 +12,7 @@ public class RoleRepresentation {
     protected String name;
     protected String description;
     protected boolean composite;
+    protected List<RoleRepresentation> composites;
 
     public RoleRepresentation() {
     }
@@ -49,4 +53,12 @@ public class RoleRepresentation {
     public void setComposite(boolean composite) {
         this.composite = composite;
     }
+
+    public List<RoleRepresentation> getComposites() {
+        return composites;
+    }
+
+    public void setComposites(List<RoleRepresentation> composites) {
+        this.composites = composites;
+    }
 }
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ApplicationResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ApplicationResource.java
index a64b45d..cfc4c62 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/ApplicationResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/ApplicationResource.java
@@ -48,7 +48,7 @@ public class ApplicationResource extends RoleContainerResource {
     }
 
     public ApplicationResource(RealmModel realm, ApplicationModel applicationModel, KeycloakSession session) {
-        super(applicationModel);
+        super(realm, applicationModel);
         this.realm = realm;
         this.application = applicationModel;
         this.session = session;
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ApplicationsResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ApplicationsResource.java
index faf90e7..4f9d5ca 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/ApplicationsResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/ApplicationsResource.java
@@ -71,7 +71,7 @@ public class ApplicationsResource {
     public ApplicationResource getApplication(final @PathParam("app-name") String name) {
         ApplicationModel applicationModel = realm.getApplicationByName(name);
         if (applicationModel == null) {
-            throw new NotFoundException();
+            throw new NotFoundException("Could not find application: " + name);
         }
         ApplicationResource applicationResource = new ApplicationResource(realm, applicationModel, session);
         resourceContext.initResource(applicationResource);
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java
index 09f2fc8..deb46fb 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java
@@ -29,7 +29,7 @@ public class RealmAdminResource extends RoleContainerResource {
     protected KeycloakSession session;
 
     public RealmAdminResource(UserModel admin, RealmModel realm) {
-        super(realm);
+        super(realm, realm);
         this.admin = admin;
         this.realm = realm;
     }
@@ -77,6 +77,14 @@ public class RealmAdminResource extends RoleContainerResource {
         return users;
     }
 
+    @Path("roles-by-id")
+    public RoleByIdResource rolesById() {
+        RoleByIdResource resource = new RoleByIdResource(realm);
+        resourceContext.initResource(resource);
+        return resource;
+
+    }
+
 
 
 }
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/RoleByIdResource.java b/services/src/main/java/org/keycloak/services/resources/admin/RoleByIdResource.java
new file mode 100755
index 0000000..98869bf
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/resources/admin/RoleByIdResource.java
@@ -0,0 +1,119 @@
+package org.keycloak.services.resources.admin;
+
+import org.jboss.resteasy.annotations.cache.NoCache;
+import org.keycloak.models.Constants;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.RoleContainerModel;
+import org.keycloak.models.RoleModel;
+import org.keycloak.representations.idm.RoleRepresentation;
+import org.keycloak.services.managers.ModelToRepresentation;
+import org.keycloak.services.resources.admin.RoleResource;
+import org.keycloak.services.resources.flows.Flows;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.NotFoundException;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriInfo;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Sometimes its easier to just interact with roles by their ID instead of container/role-name
+ *
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class RoleByIdResource extends RoleResource {
+    public RoleByIdResource(RealmModel realm) {
+        super(realm);
+    }
+
+    @Path("{role-id}")
+    @GET
+    @NoCache
+    @Produces("application/json")
+    public RoleRepresentation getRole(final @PathParam("role-id") String id) {
+        RoleModel roleModel = getRoleModel(id);
+        return getRole(roleModel);
+    }
+
+    protected RoleModel getRoleModel(String id) {
+        RoleModel roleModel = realm.getRoleById(id);
+        if (roleModel == null || roleModel.getName().startsWith(Constants.INTERNAL_ROLE)) {
+            throw new NotFoundException("Could not find role with id: " + id);
+        }
+        return roleModel;
+    }
+
+    @Path("{role-id}")
+    @DELETE
+    @NoCache
+    public void deleteRole(final @PathParam("role-id") String id) {
+        RoleModel role = getRoleModel(id);
+        deleteRole(role);
+    }
+
+    @Path("{role-id}")
+    @PUT
+    @Consumes("application/json")
+    public void updateRole(final @PathParam("role-id") String id, final RoleRepresentation rep) {
+        RoleModel role = getRoleModel(id);
+        updateRole(rep, role);
+    }
+
+    @Path("{role-id}/composites")
+    @POST
+    @Consumes("application/json")
+    public void addComposites(final @PathParam("role-id") String id, List<RoleRepresentation> roles) {
+        RoleModel role = getRoleModel(id);
+        addComposites(roles, role);
+    }
+
+    @Path("{role-id}/composites")
+    @GET
+    @NoCache
+    @Produces("application/json")
+    public Set<RoleRepresentation> getRoleComposites(final @PathParam("role-id") String id) {
+        RoleModel role = getRoleModel(id);
+        return getRoleComposites(role);
+    }
+
+    @Path("{role-id}/composites/realm")
+    @GET
+    @NoCache
+    @Produces("application/json")
+    public Set<RoleRepresentation> getRealmRoleComposites(final @PathParam("role-id") String id) {
+        RoleModel role = getRoleModel(id);
+        return getRealmRoleComposites(role);
+    }
+
+    @Path("{role-id}/composites/applications/{app}")
+    @GET
+    @NoCache
+    @Produces("application/json")
+    public Set<RoleRepresentation> getApplicationRoleComposites(final @PathParam("role-id") String id,
+                                                                final @PathParam("app") String appName) {
+        RoleModel role = getRoleModel(id);
+        return getApplicationRoleComposites(appName, role);
+    }
+
+
+    @Path("{role-id}/composites")
+    @DELETE
+    @Consumes("application/json")
+    public void deleteComposites(final @PathParam("role-id") String id, List<RoleRepresentation> roles) {
+        RoleModel role = getRoleModel(id);
+        deleteComposites(roles, role);
+    }
+
+
+}
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/RoleContainerResource.java b/services/src/main/java/org/keycloak/services/resources/admin/RoleContainerResource.java
index 1971b31..7728d61 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/RoleContainerResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/RoleContainerResource.java
@@ -1,7 +1,9 @@
 package org.keycloak.services.resources.admin;
 
 import org.jboss.resteasy.annotations.cache.NoCache;
+import org.keycloak.models.ApplicationModel;
 import org.keycloak.models.Constants;
+import org.keycloak.models.RealmModel;
 import org.keycloak.models.RoleContainerModel;
 import org.keycloak.models.RoleModel;
 import org.keycloak.representations.idm.RoleRepresentation;
@@ -22,10 +24,11 @@ import java.util.Set;
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
  * @version $Revision: 1 $
  */
-public class RoleContainerResource {
+public class RoleContainerResource extends RoleResource {
     protected RoleContainerModel roleContainer;
 
-    public RoleContainerResource(RoleContainerModel roleContainer) {
+    public RoleContainerResource(RealmModel realm, RoleContainerModel roleContainer) {
+        super(realm);
         this.roleContainer = roleContainer;
     }
 
@@ -44,6 +47,22 @@ public class RoleContainerResource {
         return roles;
     }
 
+    @Path("roles")
+    @POST
+    @Consumes("application/json")
+    public Response createRole(final @Context UriInfo uriInfo, final RoleRepresentation rep) {
+        if (roleContainer.getRole(rep.getName()) != null || rep.getName().startsWith(Constants.INTERNAL_ROLE)) {
+            return Flows.errors().exists("Role with name " + rep.getName() + " already exists");
+        }
+        RoleModel role = roleContainer.addRole(rep.getName());
+        if (role == null) {
+            throw new NotFoundException();
+        }
+        role.setDescription(rep.getDescription());
+        role.setComposite(rep.isComposite());
+        return Response.created(uriInfo.getAbsolutePathBuilder().path(role.getName()).build()).build();
+    }
+
     @Path("roles/{role-name}")
     @GET
     @NoCache
@@ -53,7 +72,7 @@ public class RoleContainerResource {
         if (roleModel == null || roleModel.getName().startsWith(Constants.INTERNAL_ROLE)) {
             throw new NotFoundException("Could not find role: " + roleName);
         }
-        return ModelToRepresentation.toRepresentation(roleModel);
+        return getRole(roleModel);
     }
 
     @Path("roles/{role-name}")
@@ -64,9 +83,7 @@ public class RoleContainerResource {
         if (role == null) {
             throw new NotFoundException("Could not find role: " + roleName);
         }
-        if (!roleContainer.removeRoleById(role.getId())) {
-            throw new NotFoundException();
-        }
+        deleteRole(role);
     }
 
     @Path("roles/{role-name}")
@@ -77,9 +94,7 @@ public class RoleContainerResource {
         if (role == null || role.getName().startsWith(Constants.INTERNAL_ROLE)) {
             throw new NotFoundException("Could not find role: " + roleName);
         }
-        role.setName(rep.getName());
-        role.setDescription(rep.getDescription());
-        role.setComposite(rep.isComposite());
+        updateRole(rep, role);
     }
 
     @Path("roles/{role-name}/composites")
@@ -90,14 +105,7 @@ public class RoleContainerResource {
         if (role == null || role.getName().startsWith(Constants.INTERNAL_ROLE)) {
             throw new NotFoundException("Could not find role: " + roleName);
         }
-        for (RoleRepresentation rep : roles) {
-            RoleModel composite = roleContainer.getRole(rep.getName());
-            if (role == null || role.getName().startsWith(Constants.INTERNAL_ROLE)) {
-                throw new NotFoundException("Could not find composite role: " + rep.getName());
-            }
-            if (!role.isComposite()) role.setComposite(true);
-            role.addCompositeRole(composite);
-        }
+        addComposites(roles, role);
     }
 
     @Path("roles/{role-name}/composites")
@@ -109,13 +117,32 @@ public class RoleContainerResource {
         if (role == null || role.getName().startsWith(Constants.INTERNAL_ROLE)) {
             throw new NotFoundException("Could not find role: " + roleName);
         }
-        if (!role.isComposite() || role.getComposites().size() == 0) return Collections.emptySet();
+        return getRoleComposites(role);
+    }
 
-        Set<RoleRepresentation> composites = new HashSet<RoleRepresentation>(role.getComposites().size());
-        for (RoleModel composite : role.getComposites()) {
-            composites.add(ModelToRepresentation.toRepresentation(composite));
+    @Path("roles/{role-name}/composites/realm")
+    @GET
+    @NoCache
+    @Produces("application/json")
+    public Set<RoleRepresentation> getRealmRoleComposites(final @PathParam("role-name") String roleName) {
+        RoleModel role = roleContainer.getRole(roleName);
+        if (role == null || role.getName().startsWith(Constants.INTERNAL_ROLE)) {
+            throw new NotFoundException("Could not find role: " + roleName);
+        }
+        return getRealmRoleComposites(role);
+    }
+
+    @Path("roles/{role-name}/composites/application/{app}")
+    @GET
+    @NoCache
+    @Produces("application/json")
+    public Set<RoleRepresentation> getApplicationRoleComposites(final @PathParam("role-name") String roleName,
+                                                                final @PathParam("app") String appName) {
+        RoleModel role = roleContainer.getRole(roleName);
+        if (role == null || role.getName().startsWith(Constants.INTERNAL_ROLE)) {
+            throw new NotFoundException("Could not find role: " + roleName);
         }
-        return composites;
+        return getApplicationRoleComposites(appName, role);
     }
 
 
@@ -127,30 +154,8 @@ public class RoleContainerResource {
         if (role == null || role.getName().startsWith(Constants.INTERNAL_ROLE)) {
             throw new NotFoundException("Could not find role: " + roleName);
         }
-        for (RoleRepresentation rep : roles) {
-            RoleModel composite = roleContainer.getRole(rep.getName());
-            if (role == null || role.getName().startsWith(Constants.INTERNAL_ROLE)) {
-                throw new NotFoundException("Could not find composite role: " + rep.getName());
-            }
-            role.removeCompositeRole(composite);
-        }
-        if (role.getComposites().size() > 0) role.setComposite(false);
+        deleteComposites(roles, role);
     }
 
 
-    @Path("roles")
-    @POST
-    @Consumes("application/json")
-    public Response createRole(final @Context UriInfo uriInfo, final RoleRepresentation rep) {
-        if (roleContainer.getRole(rep.getName()) != null || rep.getName().startsWith(Constants.INTERNAL_ROLE)) {
-            return Flows.errors().exists("Role with name " + rep.getName() + " already exists");
-        }
-        RoleModel role = roleContainer.addRole(rep.getName());
-        if (role == null) {
-            throw new NotFoundException();
-        }
-        role.setDescription(rep.getDescription());
-        role.setComposite(rep.isComposite());
-        return Response.created(uriInfo.getAbsolutePathBuilder().path(role.getName()).build()).build();
-    }
 }
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/RoleResource.java b/services/src/main/java/org/keycloak/services/resources/admin/RoleResource.java
new file mode 100755
index 0000000..8bab186
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/resources/admin/RoleResource.java
@@ -0,0 +1,101 @@
+package org.keycloak.services.resources.admin;
+
+import org.keycloak.models.ApplicationModel;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.RoleModel;
+import org.keycloak.representations.idm.RoleRepresentation;
+import org.keycloak.services.managers.ModelToRepresentation;
+
+import javax.ws.rs.NotFoundException;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class RoleResource {
+    protected RealmModel realm;
+
+    public RoleResource(RealmModel realm) {
+        this.realm = realm;
+    }
+
+    protected RoleRepresentation getRole(RoleModel roleModel) {
+        return ModelToRepresentation.toRepresentation(roleModel);
+    }
+
+    protected void deleteRole(RoleModel role) {
+        if (!role.getContainer().removeRoleById(role.getId())) {
+            throw new NotFoundException();
+        }
+    }
+
+    protected void updateRole(RoleRepresentation rep, RoleModel role) {
+        role.setName(rep.getName());
+        role.setDescription(rep.getDescription());
+        role.setComposite(rep.isComposite());
+    }
+
+    protected void addComposites(List<RoleRepresentation> roles, RoleModel role) {
+        for (RoleRepresentation rep : roles) {
+            RoleModel composite = realm.getRoleById(rep.getId());
+            if (composite == null) {
+                throw new NotFoundException("Could not find composite role: " + rep.getName());
+            }
+            if (!role.isComposite()) role.setComposite(true);
+            role.addCompositeRole(composite);
+        }
+    }
+
+    protected Set<RoleRepresentation> getRoleComposites(RoleModel role) {
+        if (!role.isComposite() || role.getComposites().size() == 0) return Collections.emptySet();
+
+        Set<RoleRepresentation> composites = new HashSet<RoleRepresentation>(role.getComposites().size());
+        for (RoleModel composite : role.getComposites()) {
+            composites.add(ModelToRepresentation.toRepresentation(composite));
+        }
+        return composites;
+    }
+
+    protected Set<RoleRepresentation> getRealmRoleComposites(RoleModel role) {
+        if (!role.isComposite() || role.getComposites().size() == 0) return Collections.emptySet();
+
+        Set<RoleRepresentation> composites = new HashSet<RoleRepresentation>(role.getComposites().size());
+        for (RoleModel composite : role.getComposites()) {
+            if (composite.getContainer() instanceof RealmModel)
+                composites.add(ModelToRepresentation.toRepresentation(composite));
+        }
+        return composites;
+    }
+
+    protected Set<RoleRepresentation> getApplicationRoleComposites(String appName, RoleModel role) {
+        if (!role.isComposite() || role.getComposites().size() == 0) return Collections.emptySet();
+
+        ApplicationModel app = realm.getApplicationByName(appName);
+        if (app == null) {
+            throw new NotFoundException("Could not find application: " + appName);
+
+        }
+
+        Set<RoleRepresentation> composites = new HashSet<RoleRepresentation>(role.getComposites().size());
+        for (RoleModel composite : role.getComposites()) {
+            if (composite.getContainer().equals(app))
+                composites.add(ModelToRepresentation.toRepresentation(composite));
+        }
+        return composites;
+    }
+
+    protected void deleteComposites(List<RoleRepresentation> roles, RoleModel role) {
+        for (RoleRepresentation rep : roles) {
+            RoleModel composite = realm.getRoleById(rep.getId());
+            if (composite == null) {
+                throw new NotFoundException("Could not find composite role: " + rep.getName());
+            }
+            role.removeCompositeRole(composite);
+        }
+        if (role.getComposites().size() == 0) role.setComposite(false);
+    }
+}