diff --git a/broker/core/src/main/java/org/keycloak/broker/provider/AbstractIdentityProvider.java b/broker/core/src/main/java/org/keycloak/broker/provider/AbstractIdentityProvider.java index e402e047061..6da630f557f 100755 --- a/broker/core/src/main/java/org/keycloak/broker/provider/AbstractIdentityProvider.java +++ b/broker/core/src/main/java/org/keycloak/broker/provider/AbstractIdentityProvider.java @@ -58,6 +58,11 @@ public abstract class AbstractIdentityProvider return null; } + @Override + public Response performLogin(AuthenticationRequest request) { + return null; + } + @Override public Response keycloakInitiatedBrowserLogout(UserSessionModel userSession, UriInfo uriInfo, RealmModel realm) { return null; diff --git a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.4.0.xml b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.4.0.xml index cad624168c2..103c7cea697 100755 --- a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.4.0.xml +++ b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.4.0.xml @@ -8,6 +8,12 @@ + + + + + + diff --git a/core/src/main/java/org/keycloak/OAuth2Constants.java b/core/src/main/java/org/keycloak/OAuth2Constants.java index 5aba901df5c..493748abf37 100644 --- a/core/src/main/java/org/keycloak/OAuth2Constants.java +++ b/core/src/main/java/org/keycloak/OAuth2Constants.java @@ -29,6 +29,8 @@ public interface OAuth2Constants { String PASSWORD = "password"; + String CLIENT_CREDENTIALS = "client_credentials"; + } diff --git a/core/src/main/java/org/keycloak/constants/ServiceAccountConstants.java b/core/src/main/java/org/keycloak/constants/ServiceAccountConstants.java new file mode 100644 index 00000000000..928f62d915b --- /dev/null +++ b/core/src/main/java/org/keycloak/constants/ServiceAccountConstants.java @@ -0,0 +1,20 @@ +package org.keycloak.constants; + +/** + * @author Marek Posolda + */ +public interface ServiceAccountConstants { + + String CLIENT_AUTH = "client_auth"; + + String SERVICE_ACCOUNT_USER_PREFIX = "service-account-"; + String SERVICE_ACCOUNT_CLIENT_ATTRIBUTE = "serviceAccountClient"; + + String CLIENT_ID_PROTOCOL_MAPPER = "Client ID"; + String CLIENT_HOST_PROTOCOL_MAPPER = "Client Host"; + String CLIENT_ADDRESS_PROTOCOL_MAPPER = "Client IP Address"; + String CLIENT_ID = "clientId"; + String CLIENT_HOST = "clientHost"; + String CLIENT_ADDRESS = "clientAddress"; + +} diff --git a/core/src/main/java/org/keycloak/representations/idm/ClientRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/ClientRepresentation.java index 0ffb9809227..e5ab5036400 100755 --- a/core/src/main/java/org/keycloak/representations/idm/ClientRepresentation.java +++ b/core/src/main/java/org/keycloak/representations/idm/ClientRepresentation.java @@ -22,6 +22,7 @@ public class ClientRepresentation { protected Integer notBefore; protected Boolean bearerOnly; protected Boolean consentRequired; + protected Boolean serviceAccountsEnabled; protected Boolean directGrantsOnly; protected Boolean publicClient; protected Boolean frontchannelLogout; @@ -144,6 +145,14 @@ public class ClientRepresentation { this.consentRequired = consentRequired; } + public Boolean isServiceAccountsEnabled() { + return serviceAccountsEnabled; + } + + public void setServiceAccountsEnabled(Boolean serviceAccountsEnabled) { + this.serviceAccountsEnabled = serviceAccountsEnabled; + } + public Boolean isDirectGrantsOnly() { return directGrantsOnly; } diff --git a/events/api/src/main/java/org/keycloak/events/Details.java b/events/api/src/main/java/org/keycloak/events/Details.java index 38ff34078aa..23cc2f7f4b8 100755 --- a/events/api/src/main/java/org/keycloak/events/Details.java +++ b/events/api/src/main/java/org/keycloak/events/Details.java @@ -35,4 +35,10 @@ public interface Details { String IMPERSONATOR_REALM = "impersonator_realm"; String IMPERSONATOR = "impersonator"; + String CLIENT_AUTH_METHOD = "client_auth_method"; + String CLIENT_AUTH_METHOD_VALUE_CLIENT_CREDENTIALS = "client_credentials"; + String CLIENT_AUTH_METHOD_VALUE_CERTIFICATE = "client_certificate"; + String CLIENT_AUTH_METHOD_VALUE_KERBEROS_KEYTAB = "kerberos_keytab"; + String CLIENT_AUTH_METHOD_VALUE_SIGNED_JWT = "signed_jwt"; + } diff --git a/events/api/src/main/java/org/keycloak/events/EventType.java b/events/api/src/main/java/org/keycloak/events/EventType.java index be72f0623b3..d8c0d193197 100755 --- a/events/api/src/main/java/org/keycloak/events/EventType.java +++ b/events/api/src/main/java/org/keycloak/events/EventType.java @@ -15,6 +15,9 @@ public enum EventType { CODE_TO_TOKEN(true), CODE_TO_TOKEN_ERROR(true), + CLIENT_LOGIN(true), + CLIENT_LOGIN_ERROR(true), + REFRESH_TOKEN(false), REFRESH_TOKEN_ERROR(false), VALIDATE_ACCESS_TOKEN(false), diff --git a/examples/demo-template/README.md b/examples/demo-template/README.md index 1a896af1915..2445e311427 100755 --- a/examples/demo-template/README.md +++ b/examples/demo-template/README.md @@ -210,6 +210,14 @@ An pure HTML5/Javascript example using Keycloak to secure it. If you are already logged in, you will not be asked for a username and password, but you will be redirected to an oauth grant page. This page asks you if you want to grant certain permissions to the third-part app. +Step 10: Service Account Example +================================ +An example for retrieve service account dedicated to the Client Application itself (not to any user). + +[http://localhost:8080/service-account-portal](http://localhost:8080/service-account-portal) + +Client authentication is done with OAuth2 Client Credentials Grant in out-of-bound request (Not Keycloak login screen displayed) + Admin Console ========================== diff --git a/examples/demo-template/pom.xml b/examples/demo-template/pom.xml index a0172c85e52..4ea8c39e81d 100755 --- a/examples/demo-template/pom.xml +++ b/examples/demo-template/pom.xml @@ -36,6 +36,7 @@ database-service third-party third-party-cdi + service-account diff --git a/examples/demo-template/service-account/pom.xml b/examples/demo-template/service-account/pom.xml new file mode 100644 index 00000000000..fde69667a00 --- /dev/null +++ b/examples/demo-template/service-account/pom.xml @@ -0,0 +1,60 @@ + + + + keycloak-examples-demo-parent + org.keycloak + 1.4.0.Final-SNAPSHOT + + + 4.0.0 + org.keycloak.example.demo + service-account-example + war + Service Account Example App + + + + + org.jboss.spec.javax.servlet + jboss-servlet-api_3.0_spec + provided + + + org.keycloak + keycloak-core + provided + + + org.keycloak + keycloak-adapter-core + provided + + + org.apache.httpcomponents + httpclient + provided + + + + + service-account-portal + + + org.jboss.as.plugins + jboss-as-maven-plugin + + false + + + + org.wildfly.plugins + wildfly-maven-plugin + + false + + + + + + \ No newline at end of file diff --git a/examples/demo-template/service-account/src/main/java/org/keycloak/example/ProductServiceAccountServlet.java b/examples/demo-template/service-account/src/main/java/org/keycloak/example/ProductServiceAccountServlet.java new file mode 100644 index 00000000000..f9dc9f165e6 --- /dev/null +++ b/examples/demo-template/service-account/src/main/java/org/keycloak/example/ProductServiceAccountServlet.java @@ -0,0 +1,234 @@ +package org.keycloak.example; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.http.HttpEntity; +import org.apache.http.HttpResponse; +import org.apache.http.NameValuePair; +import org.apache.http.client.HttpClient; +import org.apache.http.client.entity.UrlEncodedFormEntity; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.impl.client.DefaultHttpClient; +import org.apache.http.message.BasicNameValuePair; +import org.keycloak.OAuth2Constants; +import org.keycloak.RSATokenVerifier; +import org.keycloak.VerificationException; +import org.keycloak.adapters.KeycloakDeployment; +import org.keycloak.adapters.KeycloakDeploymentBuilder; +import org.keycloak.adapters.ServerRequest; +import org.keycloak.constants.ServiceAccountConstants; +import org.keycloak.representations.AccessToken; +import org.keycloak.representations.AccessTokenResponse; +import org.keycloak.util.BasicAuthHelper; +import org.keycloak.util.JsonSerialization; + +/** + * @author Marek Posolda + */ +public class ProductServiceAccountServlet extends HttpServlet { + + public static final String ERROR = "error"; + public static final String TOKEN = "token"; + public static final String TOKEN_PARSED = "idTokenParsed"; + public static final String REFRESH_TOKEN = "refreshToken"; + public static final String PRODUCTS = "products"; + + @Override + public void init() throws ServletException { + InputStream config = getServletContext().getResourceAsStream("WEB-INF/keycloak.json"); + KeycloakDeployment deployment = KeycloakDeploymentBuilder.build(config); + HttpClient client = new DefaultHttpClient(); + + getServletContext().setAttribute(KeycloakDeployment.class.getName(), deployment); + getServletContext().setAttribute(HttpClient.class.getName(), client); + } + + @Override + public void destroy() { + getHttpClient().getConnectionManager().shutdown(); + } + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + String reqUri = req.getRequestURI(); + if (reqUri.endsWith("/login")) { + serviceAccountLogin(req); + } else if (reqUri.endsWith("/refresh")) { + refreshToken(req); + } else if (reqUri.endsWith("/logout")){ + logout(req); + } + + // Don't load products if some error happened during login,refresh or logout + if (req.getAttribute(ERROR) == null) { + loadProducts(req); + } + + req.getRequestDispatcher("/WEB-INF/page.jsp").forward(req, resp); + } + + private void serviceAccountLogin(HttpServletRequest req) { + KeycloakDeployment deployment = getKeycloakDeployment(); + HttpClient client = getHttpClient(); + + String clientId = deployment.getResourceName(); + String clientSecret = deployment.getResourceCredentials().get("secret"); + + try { + HttpPost post = new HttpPost(deployment.getTokenUrl()); + List formparams = new ArrayList(); + formparams.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, OAuth2Constants.CLIENT_CREDENTIALS)); + + String authHeader = BasicAuthHelper.createHeader(clientId, clientSecret); + post.addHeader("Authorization", authHeader); + + UrlEncodedFormEntity form = new UrlEncodedFormEntity(formparams, "UTF-8"); + post.setEntity(form); + + HttpResponse response = client.execute(post); + int status = response.getStatusLine().getStatusCode(); + HttpEntity entity = response.getEntity(); + if (status != 200) { + String json = getContent(entity); + String error = "Service account login failed. Bad status: " + status + " response: " + json; + req.setAttribute(ERROR, error); + } else if (entity == null) { + req.setAttribute(ERROR, "No entity"); + } else { + String json = getContent(entity); + AccessTokenResponse tokenResp = JsonSerialization.readValue(json, AccessTokenResponse.class); + setTokens(req, deployment, tokenResp); + } + } catch (IOException ioe) { + ioe.printStackTrace(); + req.setAttribute(ERROR, "Service account login failed. IOException occured. See server.log for details. Message is: " + ioe.getMessage()); + } catch (VerificationException vfe) { + req.setAttribute(ERROR, "Service account login failed. Failed to verify token Message is: " + vfe.getMessage()); + } + } + + private void setTokens(HttpServletRequest req, KeycloakDeployment deployment, AccessTokenResponse tokenResponse) throws IOException, VerificationException { + String token = tokenResponse.getToken(); + String refreshToken = tokenResponse.getRefreshToken(); + AccessToken tokenParsed = RSATokenVerifier.verifyToken(token, deployment.getRealmKey(), deployment.getRealmInfoUrl()); + req.getSession().setAttribute(TOKEN, token); + req.getSession().setAttribute(REFRESH_TOKEN, refreshToken); + req.getSession().setAttribute(TOKEN_PARSED, tokenParsed); + } + + private void loadProducts(HttpServletRequest req) { + HttpClient client = getHttpClient(); + String token = (String) req.getSession().getAttribute(TOKEN); + + HttpGet get = new HttpGet("http://localhost:8080/database/products"); + if (token != null) { + get.addHeader("Authorization", "Bearer " + token); + } + try { + HttpResponse response = client.execute(get); + HttpEntity entity = response.getEntity(); + int status = response.getStatusLine().getStatusCode(); + if (status != 200) { + String json = getContent(entity); + String error = "Failed retrieve products."; + + if (status == 401) { + error = error + " You need to login first with the service account."; + } else if (status == 403) { + error = error + " Maybe service account user doesn't have needed role? Assign role 'user' in Keycloak admin console to user '" + + ServiceAccountConstants.SERVICE_ACCOUNT_USER_PREFIX + getKeycloakDeployment().getResourceName() + "' and then logout and login again."; + } + error = error + " Status: " + status + ", Response: " + json; + req.setAttribute(ERROR, error); + } else if (entity == null) { + req.setAttribute(ERROR, "No entity"); + } else { + String products = getContent(entity); + req.setAttribute(PRODUCTS, products); + } + } catch (IOException ioe) { + ioe.printStackTrace(); + req.setAttribute(ERROR, "Failed retrieve products. IOException occured. See server.log for details. Message is: " + ioe.getMessage()); + } + } + + private void refreshToken(HttpServletRequest req) { + KeycloakDeployment deployment = getKeycloakDeployment(); + String refreshToken = (String) req.getSession().getAttribute(REFRESH_TOKEN); + if (refreshToken == null) { + req.setAttribute(ERROR, "No refresh token available. Please login first"); + } else { + try { + AccessTokenResponse tokenResponse = ServerRequest.invokeRefresh(deployment, refreshToken); + setTokens(req, deployment, tokenResponse); + } catch (ServerRequest.HttpFailure hfe) { + hfe.printStackTrace(); + req.setAttribute(ERROR, "Failed refresh token. See server.log for details. Status was: " + hfe.getStatus() + ", Error is: " + hfe.getError()); + } catch (Exception ioe) { + ioe.printStackTrace(); + req.setAttribute(ERROR, "Failed refresh token. See server.log for details. Message is: " + ioe.getMessage()); + } + } + } + + private void logout(HttpServletRequest req) { + KeycloakDeployment deployment = getKeycloakDeployment(); + String refreshToken = (String) req.getSession().getAttribute(REFRESH_TOKEN); + if (refreshToken == null) { + req.setAttribute(ERROR, "No refresh token available. Please login first"); + } else { + try { + ServerRequest.invokeLogout(deployment, refreshToken); + req.getSession().removeAttribute(TOKEN); + req.getSession().removeAttribute(REFRESH_TOKEN); + req.getSession().removeAttribute(TOKEN_PARSED); + } catch (IOException ioe) { + ioe.printStackTrace(); + req.setAttribute(ERROR, "Failed refresh token. See server.log for details. Message is: " + ioe.getMessage()); + } catch (ServerRequest.HttpFailure hfe) { + hfe.printStackTrace(); + req.setAttribute(ERROR, "Failed refresh token. See server.log for details. Status was: " + hfe.getStatus() + ", Error is: " + hfe.getError()); + } + } + } + + private String getContent(HttpEntity entity) throws IOException { + if (entity == null) return null; + InputStream is = entity.getContent(); + try { + ByteArrayOutputStream os = new ByteArrayOutputStream(); + int c; + while ((c = is.read()) != -1) { + os.write(c); + } + byte[] bytes = os.toByteArray(); + String data = new String(bytes); + return data; + } finally { + try { + is.close(); + } catch (IOException ignored) { + + } + } + + } + + private KeycloakDeployment getKeycloakDeployment() { + return (KeycloakDeployment) getServletContext().getAttribute(KeycloakDeployment.class.getName()); + } + + private HttpClient getHttpClient() { + return (HttpClient) getServletContext().getAttribute(HttpClient.class.getName()); + } +} diff --git a/examples/demo-template/service-account/src/main/webapp/WEB-INF/jboss-deployment-structure.xml b/examples/demo-template/service-account/src/main/webapp/WEB-INF/jboss-deployment-structure.xml new file mode 100644 index 00000000000..9c1bac9b368 --- /dev/null +++ b/examples/demo-template/service-account/src/main/webapp/WEB-INF/jboss-deployment-structure.xml @@ -0,0 +1,9 @@ + + + + + + + + \ No newline at end of file diff --git a/examples/demo-template/service-account/src/main/webapp/WEB-INF/keycloak.json b/examples/demo-template/service-account/src/main/webapp/WEB-INF/keycloak.json new file mode 100644 index 00000000000..7eec22a6c3c --- /dev/null +++ b/examples/demo-template/service-account/src/main/webapp/WEB-INF/keycloak.json @@ -0,0 +1,10 @@ +{ + "realm" : "demo", + "realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB", + "auth-server-url" : "http://localhost:8080/auth", + "ssl-required" : "external", + "resource" : "product-sa-client", + "credentials": { + "secret": "password" + } +} \ No newline at end of file diff --git a/examples/demo-template/service-account/src/main/webapp/WEB-INF/page.jsp b/examples/demo-template/service-account/src/main/webapp/WEB-INF/page.jsp new file mode 100644 index 00000000000..e151f96b49f --- /dev/null +++ b/examples/demo-template/service-account/src/main/webapp/WEB-INF/page.jsp @@ -0,0 +1,52 @@ +<%@ page language="java" contentType="text/html; charset=ISO-8859-1" + pageEncoding="ISO-8859-1" %> +<%@ page import="org.keycloak.example.ProductServiceAccountServlet" %> +<%@ page import="org.keycloak.representations.AccessToken" %> +<%@ page import="org.keycloak.constants.ServiceAccountConstants" %> +<%@ page import="org.keycloak.util.Time" %> + + + Service account portal + + +<% + AccessToken token = (AccessToken) request.getSession().getAttribute(ProductServiceAccountServlet.TOKEN_PARSED); + String products = (String) request.getAttribute(ProductServiceAccountServlet.PRODUCTS); + String appError = (String) request.getAttribute(ProductServiceAccountServlet.ERROR); +%> +

Service account portal

+

Login | Refresh token | Logout

+
+ +<% if (appError != null) { %> +

+ Error: <%= appError %> +

+
+<% } %> + +<% if (token != null) { %> +

+ Service account available
+ Client ID: <%= token.getOtherClaims().get(ServiceAccountConstants.CLIENT_ID) %>
+ Client hostname: <%= token.getOtherClaims().get(ServiceAccountConstants.CLIENT_HOST) %>
+ Client address: <%= token.getOtherClaims().get(ServiceAccountConstants.CLIENT_ADDRESS) %>
+ Token expiration: <%= Time.toDate(token.getExpiration()) %>
+ <% if (token.isExpired()) { %> + Access token is expired. You may need to refresh
+ <% } %> +

+
+<% } %> + +<% if (products != null) { %> +

+ Products retrieved successfully from REST endpoint
+ Product list: <%= products %> +

+
+<% } %> + + + \ No newline at end of file diff --git a/examples/demo-template/service-account/src/main/webapp/WEB-INF/web.xml b/examples/demo-template/service-account/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 00000000000..5dc7103ac03 --- /dev/null +++ b/examples/demo-template/service-account/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,19 @@ + + + + service-account-portal + + + ServiceAccountExample + org.keycloak.example.ProductServiceAccountServlet + + + + ServiceAccountExample + /app/* + + + \ No newline at end of file diff --git a/examples/demo-template/service-account/src/main/webapp/index.html b/examples/demo-template/service-account/src/main/webapp/index.html new file mode 100644 index 00000000000..e2820d1744a --- /dev/null +++ b/examples/demo-template/service-account/src/main/webapp/index.html @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/examples/demo-template/testrealm.json b/examples/demo-template/testrealm.json index d592010021a..a26a0582097 100755 --- a/examples/demo-template/testrealm.json +++ b/examples/demo-template/testrealm.json @@ -162,6 +162,12 @@ "publicClient": true, "directGrantsOnly": true, "consentRequired": true + }, + { + "clientId": "product-sa-client", + "enabled": true, + "secret": "password", + "serviceAccountsEnabled": true } ], "clientScopeMappings": { diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/app.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/app.js index 3f8618bf76d..5ddf659deb2 100755 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/app.js +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/app.js @@ -762,6 +762,18 @@ module.config([ '$routeProvider', function($routeProvider) { }, controller : 'ClientInstallationCtrl' }) + .when('/realms/:realm/clients/:client/service-accounts', { + templateUrl : resourceUrl + '/partials/client-service-accounts.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + client : function(ClientLoader) { + return ClientLoader(); + } + }, + controller : 'ClientServiceAccountsCtrl' + }) .when('/create/client/:realm', { templateUrl : resourceUrl + '/partials/client-detail.html', resolve : { @@ -1594,6 +1606,24 @@ module.directive('kcNavigationUser', function () { } }); +module.directive('kcTabsIdentityProvider', function () { + return { + scope: true, + restrict: 'E', + replace: true, + templateUrl: resourceUrl + '/templates/kc-tabs-identity-provider.html' + } +}); + +module.directive('kcTabsUserFederation', function () { + return { + scope: true, + restrict: 'E', + replace: true, + templateUrl: resourceUrl + '/templates/kc-tabs-user-federation.html' + } +}); + module.controller('RoleSelectorModalCtrl', function($scope, realm, config, configName, RealmRoles, Client, ClientRole, $modalInstance) { console.log('realm: ' + realm.realm); $scope.selectedRealmRole = { @@ -1812,4 +1842,20 @@ module.directive('kcTooltip', function($compile) { $compile(label)(scope); } }; +}); + +module.directive( 'kcOpen', function ( $location ) { + return function ( scope, element, attrs ) { + var path; + + attrs.$observe( 'kcOpen', function (val) { + path = val; + }); + + element.bind( 'click', function () { + scope.$apply( function () { + $location.path(path); + }); + }); + }; }); \ No newline at end of file diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js index 5b3ddde8b61..13639198b2b 100755 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js @@ -4,6 +4,20 @@ Array.prototype.remove = function(from, to) { return this.push.apply(this, rest); }; +module.controller('ClientTabCtrl', function(Dialog, $scope, Current, Notifications, $location) { + $scope.removeClient = function() { + Dialog.confirmDelete($scope.client.clientId, 'client', function() { + $scope.client.$remove({ + realm : Current.realm.realm, + client : $scope.client.id + }, function() { + $location.url("/realms/" + Current.realm.realm + "/clients"); + Notifications.success("The client has been deleted."); + }); + }); + }; +}); + module.controller('ClientRoleListCtrl', function($scope, $location, realm, client, roles) { $scope.realm = realm; $scope.roles = roles; @@ -834,20 +848,6 @@ module.controller('ClientDetailCtrl', function($scope, realm, client, $route, se $scope.cancel = function() { $location.url("/realms/" + realm.realm + "/clients"); }; - - $scope.remove = function() { - Dialog.confirmDelete($scope.client.clientId, 'client', function() { - $scope.client.$remove({ - realm : realm.realm, - client : $scope.client.id - }, function() { - $location.url("/realms/" + realm.realm + "/clients"); - Notifications.success("The client has been deleted."); - }); - }); - }; - - }); module.controller('ClientScopeMappingCtrl', function($scope, $http, realm, client, clients, Notifications, @@ -1298,6 +1298,25 @@ module.controller('ClientProtocolMapperCreateCtrl', function($scope, realm, serv }); +module.controller('ClientServiceAccountsCtrl', function($scope, $http, realm, client, Notifications, Client) { + $scope.realm = realm; + $scope.client = angular.copy(client); + + $scope.serviceAccountsEnabledChanged = function() { + if (client.serviceAccountsEnabled != $scope.client.serviceAccountsEnabled) { + Client.update({ + realm : realm.realm, + client : client.id + }, $scope.client, function() { + $scope.changed = false; + client = angular.copy($scope.client); + Notifications.success("Service Account settings updated."); + }); + } + } + +}); + diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js index 59a3f8518b9..84effdad564 100755 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js @@ -83,7 +83,7 @@ module.controller('GlobalCtrl', function($scope, $http, Auth, WhoAmI, Current, $ get impersonation() { return getAccess('impersonation'); } - } + }; $scope.$watch(function() { return $location.path(); @@ -113,6 +113,18 @@ module.controller('HomeCtrl', function(Realm, Auth, $location) { }); }); +module.controller('RealmTabCtrl', function(Dialog, $scope, Current, Realm, Notifications, $location) { + $scope.removeRealm = function() { + Dialog.confirmDelete(Current.realm.realm, 'realm', function() { + Realm.remove({ id : Current.realm.realm }, function() { + Current.realms = Realm.query(); + Notifications.success("The realm has been deleted."); + $location.url("/"); + }); + }); + }; +}); + module.controller('RealmListCtrl', function($scope, Realm, Current) { $scope.realms = Realm.query(); Current.realms = $scope.realms; @@ -286,16 +298,6 @@ module.controller('RealmDetailCtrl', function($scope, Current, Realm, realm, ser $scope.cancel = function() { window.history.back(); }; - - $scope.remove = function() { - Dialog.confirmDelete($scope.realm.realm, 'realm', function() { - Realm.remove({ id : $scope.realm.realm }, function() { - Current.realms = Realm.query(); - Notifications.success("The realm has been deleted."); - $location.url("/"); - }); - }); - }; }); function genericRealmUpdate($scope, Current, Realm, realm, serverInfo, $http, $location, Dialog, Notifications, url) { @@ -593,6 +595,22 @@ module.controller('RealmDefaultRolesCtrl', function ($scope, Realm, realm, clien }); + + +module.controller('IdentityProviderTabCtrl', function(Dialog, $scope, Current, Notifications, $location) { + $scope.removeIdentityProvider = function() { + Dialog.confirmDelete($scope.identityProvider.alias, 'provider', function() { + $scope.identityProvider.$remove({ + realm : Current.realm.realm, + alias : $scope.identityProvider.alias + }, function() { + $location.url("/realms/" + Current.realm.realm + "/identity-provider-settings"); + Notifications.success("The identity provider has been deleted."); + }); + }); + }; +}); + module.controller('RealmIdentityProviderCtrl', function($scope, $filter, $upload, $http, $route, realm, instance, providerFactory, IdentityProvider, serverInfo, $location, Notifications, Dialog) { console.log('RealmIdentityProviderCtrl'); @@ -802,18 +820,6 @@ module.controller('RealmIdentityProviderCtrl', function($scope, $filter, $upload $location.url("/create/identity-provider/" + realm.realm + "/" + provider.id); }; - $scope.remove = function() { - Dialog.confirmDelete($scope.identityProvider.alias, 'provider', function() { - $scope.identityProvider.$remove({ - realm : realm.realm, - alias : $scope.identityProvider.alias - }, function() { - $location.url("/realms/" + realm.realm + "/identity-provider-settings"); - Notifications.success("The client has been deleted."); - }); - }); - }; - $scope.save = function() { if ($scope.newIdentityProvider) { if (!$scope.identityProvider.alias) { diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/users.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/users.js index 373e0cc2158..f3fe77f4caa 100755 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/users.js +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/users.js @@ -273,6 +273,21 @@ module.controller('UserListCtrl', function($scope, realm, User, UserImpersonatio }); +module.controller('UserTabCtrl', function($scope, $location, Dialog, Notifications, Current) { + $scope.removeUser = function() { + Dialog.confirmDelete($scope.user.id, 'user', function() { + $scope.user.$remove({ + realm : Current.realm.realm, + userId : $scope.user.id + }, function() { + $location.url("/realms/" + Current.realm.realm + "/users"); + Notifications.success("The user has been deleted."); + }, function() { + Notifications.error("User couldn't be deleted"); + }); + }); + }; +}); module.controller('UserDetailCtrl', function($scope, realm, user, BruteForceUser, User, UserFederationInstances, UserImpersonation, RequiredActions, $location, Dialog, Notifications) { $scope.realm = realm; @@ -420,20 +435,6 @@ module.controller('UserDetailCtrl', function($scope, realm, user, BruteForceUser $scope.cancel = function() { $location.url("/realms/" + realm.realm + "/users"); }; - - $scope.remove = function() { - Dialog.confirmDelete($scope.user.id, 'user', function() { - $scope.user.$remove({ - realm : realm.realm, - userId : $scope.user.id - }, function() { - $location.url("/realms/" + realm.realm + "/users"); - Notifications.success("The user has been deleted."); - }, function() { - Notifications.error("User couldn't be deleted"); - }); - }); - }; }); module.controller('UserCredentialsCtrl', function($scope, realm, user, User, UserCredentials, Notifications, Dialog) { @@ -544,11 +545,27 @@ module.controller('UserFederationCtrl', function($scope, $location, realm, UserF }); +module.controller('UserFederationTabCtrl', function(Dialog, $scope, Current, Notifications, $location) { + $scope.removeUserFederation = function() { + Dialog.confirm('Delete', 'Are you sure you want to permanently delete this provider? All imported users will also be deleted.', function() { + $scope.instance.$remove({ + realm : Current.realm.realm, + instance : $scope.instance.id + }, function() { + $location.url("/realms/" + Current.realm.realm + "/user-federation"); + Notifications.success("The provider has been deleted."); + }); + }); + }; +}); + + module.controller('GenericUserFederationCtrl', function($scope, $location, Notifications, $route, Dialog, realm, instance, providerFactory, UserFederationInstances, UserFederationSync) { console.log('GenericUserFederationCtrl'); $scope.create = !instance.providerName; $scope.providerFactory = providerFactory; + $scope.provider = instance; console.log("providerFactory: " + providerFactory.id); @@ -644,18 +661,6 @@ module.controller('GenericUserFederationCtrl', function($scope, $location, Notif } }; - $scope.remove = function() { - Dialog.confirm('Delete', 'Are you sure you want to permanently delete this provider? All imported users will also be deleted.', function() { - $scope.instance.$remove({ - realm : realm.realm, - instance : $scope.instance.id - }, function() { - $location.url("/realms/" + realm.realm + "/user-federation"); - Notifications.success("The provider has been deleted."); - }); - }); - }; - $scope.triggerFullSync = function() { console.log('GenericCtrl: triggerFullSync'); triggerSync('triggerFullSync'); @@ -906,6 +911,7 @@ module.controller('UserFederationMapperListCtrl', function($scope, $location, No $scope.realm = realm; $scope.provider = provider; + $scope.instance = provider; $scope.mapperTypes = mapperTypes; $scope.mappers = mappers; diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/authenticator-config.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/authenticator-config.html index 9060eeddc2b..585680616ba 100755 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/authenticator-config.html +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/authenticator-config.html @@ -38,8 +38,8 @@
- - + +
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/brute-force.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/brute-force.html index a4f6db0b875..408794a6ee3 100755 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/brute-force.html +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/brute-force.html @@ -1,6 +1,4 @@
-

Settings

-
\ No newline at end of file diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-mappers.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-mappers.html index b62902d77f1..88be5e1868d 100755 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-mappers.html +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-mappers.html @@ -5,8 +5,6 @@
  • {{client.clientId}}
  • -

    {{client.clientId|capitalize}}

    - diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-revocation.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-revocation.html index 962b8029046..6a0c6397434 100755 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-revocation.html +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-revocation.html @@ -5,8 +5,6 @@
  • {{client.clientId}}
  • -

    {{client.clientId|capitalize}}

    - diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-role-detail.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-role-detail.html index 1c11d57377d..c0ce7663017 100755 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-role-detail.html +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-role-detail.html @@ -43,14 +43,14 @@
    - +
    - - + +
    diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-role-list.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-role-list.html index e6e7f2c53eb..f07d9e3fc25 100755 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-role-list.html +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-role-list.html @@ -5,14 +5,12 @@
  • {{client.clientId}}
  • -

    {{client.clientId|capitalize}}

    -
    - + @@ -29,6 +28,9 @@ + diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-scope-mappings.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-scope-mappings.html index b5a26271c85..6f68ed81d4f 100755 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-scope-mappings.html +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-scope-mappings.html @@ -5,8 +5,6 @@
  • {{client.clientId}}
  • -

    {{client.clientId|capitalize}}

    -

    {{client.clientId}} Scope Mappings

    diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-service-accounts.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-service-accounts.html new file mode 100644 index 00000000000..1e5f0e5790a --- /dev/null +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-service-accounts.html @@ -0,0 +1,28 @@ +
    + + + +

    {{client.clientId|capitalize}}

    + + + +

    {{client.clientId}} Service Accounts

    +

    + +
    +
    + + Allows you to authenticate this client to Keycloak and retrieve access token dedicated to this client. +
    + +
    +
    +
    + + +
    + + \ No newline at end of file diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-sessions.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-sessions.html index 846cd923265..9c1dc586b3b 100755 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-sessions.html +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-sessions.html @@ -5,8 +5,6 @@
  • {{client.clientId}}
  • -

    {{client.clientId|capitalize}}

    - diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/defense-headers.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/defense-headers.html index d9f3b2c6218..c6bd5b76051 100755 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/defense-headers.html +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/defense-headers.html @@ -1,6 +1,4 @@
    -

    Settings

    -
    + @@ -22,6 +20,7 @@ Role Name Composite DescriptionActions
    {{role.name}} {{role.composite}} {{role.description}} + +
    No client roles available
    diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/identity-provider-mapper-detail.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/identity-provider-mapper-detail.html index 1b46810a0f9..2c389d7b12d 100755 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/identity-provider-mapper-detail.html +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/identity-provider-mapper-detail.html @@ -47,14 +47,19 @@ -
    - - + +
    +
    + + +
    -
    - - +
    +
    + + +
    diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/identity-provider-mappers.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/identity-provider-mappers.html index 24409e2c067..676a1166e38 100755 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/identity-provider-mappers.html +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/identity-provider-mappers.html @@ -4,13 +4,7 @@
  • {{identityProvider.alias}}
  • -

    {{identityProvider.alias|capitalize}}

    - - +
    diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/password-policy.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/password-policy.html index f9d7c4354c1..ff4e0f7f153 100755 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/password-policy.html +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/password-policy.html @@ -4,49 +4,46 @@ -
    - Realm Password Policy Specify required password format. You can also set how many times a password is hashed before it is stored in database. Multiple Regex patterns, separated by comma, can be added. -
    - - - - + + + + + + + + + + + + + + +
    -
    -
    - -
    + + + + + - - - - - - - - - - - - - - -
    +
    +
    +
    -
    Policy TypePolicy ValueActions
    {{p.name|capitalize}} - - -
    -
    - +
    +
    Policy TypePolicy ValueActions
    {{p.name|capitalize}} + + + +
    -
    - - +
    + +
    diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/protocol-mapper-detail.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/protocol-mapper-detail.html index 27e49cd1b37..5bee1b5b6de 100755 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/protocol-mapper-detail.html +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/protocol-mapper-detail.html @@ -81,8 +81,8 @@
    - - + +
    diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-cache-settings.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-cache-settings.html index 74febd76255..c7f9b262109 100755 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-cache-settings.html +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-cache-settings.html @@ -1,6 +1,4 @@
    -

    Settings

    -
    @@ -21,8 +19,8 @@
    - - + +
    diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-create.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-create.html index c28f12b5a98..630188d8473 100755 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-create.html +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-create.html @@ -1,7 +1,4 @@
    - -

    Add Realm

    -
    Import Realm @@ -15,10 +12,10 @@ {{files[0].name}}
    -
    +
    - - + +
    @@ -44,7 +41,7 @@
    - +
    diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-detail.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-detail.html index 14ea2bc32ee..e1ff6834a62 100755 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-detail.html +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-detail.html @@ -1,7 +1,4 @@
    -

    Settings

    -

    Add Realm

    -
    @@ -27,9 +24,8 @@
    - - - + +
    diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-export.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-export.html index c79b92b917f..e811dc9d4bd 100755 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-export.html +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-export.html @@ -4,14 +4,7 @@
  • {{identityProvider.alias}}
  • -

    {{identityProvider.alias|capitalize}}

    -

    Add OpenID Connect Identity Provider

    - - +
    diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-oidc.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-oidc.html index 2fad5c99b98..23ad5207c00 100755 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-oidc.html +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-oidc.html @@ -4,9 +4,7 @@
  • {{identityProvider.alias}}
  • -

    {{identityProvider.alias|capitalize}}

    -

    Add OpenID Connect Identity Provider

    +