conflict
This commit is contained in:
commit
7dc05a45ac
107 changed files with 1616 additions and 375 deletions
|
@ -58,6 +58,11 @@ public abstract class AbstractIdentityProvider<C extends IdentityProviderModel>
|
|||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response performLogin(AuthenticationRequest request) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response keycloakInitiatedBrowserLogout(UserSessionModel userSession, UriInfo uriInfo, RealmModel realm) {
|
||||
return null;
|
||||
|
|
|
@ -8,6 +8,12 @@
|
|||
<delete tableName="CLIENT_SESSION"/>
|
||||
<delete tableName="USER_SESSION_NOTE"/>
|
||||
<delete tableName="USER_SESSION"/>
|
||||
|
||||
<addColumn tableName="CLIENT">
|
||||
<column name="SERVICE_ACCOUNTS_ENABLED" type="BOOLEAN" defaultValueBoolean="false">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</addColumn>
|
||||
<addColumn tableName="CLIENT_SESSION">
|
||||
<column name="CURRENT_ACTION" type="VARCHAR(36)">
|
||||
<constraints nullable="true"/>
|
||||
|
|
|
@ -29,6 +29,8 @@ public interface OAuth2Constants {
|
|||
|
||||
String PASSWORD = "password";
|
||||
|
||||
String CLIENT_CREDENTIALS = "client_credentials";
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
package org.keycloak.constants;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
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";
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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";
|
||||
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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
|
||||
==========================
|
||||
|
||||
|
|
|
@ -36,6 +36,7 @@
|
|||
<module>database-service</module>
|
||||
<module>third-party</module>
|
||||
<module>third-party-cdi</module>
|
||||
<module>service-account</module>
|
||||
</modules>
|
||||
|
||||
<profiles>
|
||||
|
|
60
examples/demo-template/service-account/pom.xml
Normal file
60
examples/demo-template/service-account/pom.xml
Normal file
|
@ -0,0 +1,60 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<parent>
|
||||
<artifactId>keycloak-examples-demo-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>1.4.0.Final-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>org.keycloak.example.demo</groupId>
|
||||
<artifactId>service-account-example</artifactId>
|
||||
<packaging>war</packaging>
|
||||
<name>Service Account Example App</name>
|
||||
<description/>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.jboss.spec.javax.servlet</groupId>
|
||||
<artifactId>jboss-servlet-api_3.0_spec</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-core</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-adapter-core</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpclient</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<finalName>service-account-portal</finalName>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.jboss.as.plugins</groupId>
|
||||
<artifactId>jboss-as-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<skip>false</skip>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.wildfly.plugins</groupId>
|
||||
<artifactId>wildfly-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<skip>false</skip>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
|
@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
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<NameValuePair> formparams = new ArrayList<NameValuePair>();
|
||||
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());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
<jboss-deployment-structure>
|
||||
<deployment>
|
||||
<dependencies>
|
||||
<!-- the Demo code uses classes in these modules. These are optional to import if you are not using
|
||||
Apache Http Client or the HttpClientBuilder that comes with the adapter core -->
|
||||
<module name="org.apache.httpcomponents"/>
|
||||
</dependencies>
|
||||
</deployment>
|
||||
</jboss-deployment-structure>
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -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" %>
|
||||
<html>
|
||||
<head>
|
||||
<title>Service account portal</title>
|
||||
</head>
|
||||
<body bgcolor="#FFFFFF">
|
||||
<%
|
||||
AccessToken token = (AccessToken) request.getSession().getAttribute(ProductServiceAccountServlet.TOKEN_PARSED);
|
||||
String products = (String) request.getAttribute(ProductServiceAccountServlet.PRODUCTS);
|
||||
String appError = (String) request.getAttribute(ProductServiceAccountServlet.ERROR);
|
||||
%>
|
||||
<h1>Service account portal</h1>
|
||||
<p><a href="/service-account-portal/app/login">Login</a> | <a href="/service-account-portal/app/refresh">Refresh token</a> | <a
|
||||
href="/service-account-portal/app/logout">Logout</a></p>
|
||||
<hr />
|
||||
|
||||
<% if (appError != null) { %>
|
||||
<p><font color="red">
|
||||
<b>Error: </b> <%= appError %>
|
||||
</font></p>
|
||||
<hr />
|
||||
<% } %>
|
||||
|
||||
<% if (token != null) { %>
|
||||
<p>
|
||||
<b>Service account available</b><br />
|
||||
Client ID: <%= token.getOtherClaims().get(ServiceAccountConstants.CLIENT_ID) %><br />
|
||||
Client hostname: <%= token.getOtherClaims().get(ServiceAccountConstants.CLIENT_HOST) %><br />
|
||||
Client address: <%= token.getOtherClaims().get(ServiceAccountConstants.CLIENT_ADDRESS) %><br />
|
||||
Token expiration: <%= Time.toDate(token.getExpiration()) %><br />
|
||||
<% if (token.isExpired()) { %>
|
||||
<font color="red">Access token is expired. You may need to refresh</font><br />
|
||||
<% } %>
|
||||
</p>
|
||||
<hr />
|
||||
<% } %>
|
||||
|
||||
<% if (products != null) { %>
|
||||
<p>
|
||||
<b>Products retrieved successfully from REST endpoint</b><br />
|
||||
Product list: <%= products %>
|
||||
</p>
|
||||
<hr />
|
||||
<% } %>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,19 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
|
||||
version="3.0">
|
||||
|
||||
<module-name>service-account-portal</module-name>
|
||||
|
||||
<servlet>
|
||||
<servlet-name>ServiceAccountExample</servlet-name>
|
||||
<servlet-class>org.keycloak.example.ProductServiceAccountServlet</servlet-class>
|
||||
</servlet>
|
||||
|
||||
<servlet-mapping>
|
||||
<servlet-name>ServiceAccountExample</servlet-name>
|
||||
<url-pattern>/app/*</url-pattern>
|
||||
</servlet-mapping>
|
||||
|
||||
</web-app>
|
|
@ -0,0 +1,5 @@
|
|||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Refresh" content="0; URL=app">
|
||||
</head>
|
||||
</html>
|
|
@ -162,6 +162,12 @@
|
|||
"publicClient": true,
|
||||
"directGrantsOnly": true,
|
||||
"consentRequired": true
|
||||
},
|
||||
{
|
||||
"clientId": "product-sa-client",
|
||||
"enabled": true,
|
||||
"secret": "password",
|
||||
"serviceAccountsEnabled": true
|
||||
}
|
||||
],
|
||||
"clientScopeMappings": {
|
||||
|
|
|
@ -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 = {
|
||||
|
@ -1813,3 +1843,19 @@ module.directive('kcTooltip', function($compile) {
|
|||
}
|
||||
};
|
||||
});
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
};
|
||||
});
|
|
@ -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.");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -38,8 +38,8 @@
|
|||
|
||||
<div class="form-group">
|
||||
<div class="col-md-10 col-md-offset-2" data-ng-show="!create && access.manageRealm">
|
||||
<button kc-save data-ng-show="changed">Save</button>
|
||||
<button kc-reset data-ng-show="changed">Cancel</button>
|
||||
<button kc-save data-ng-disabled="!changed">Save</button>
|
||||
<button kc-reset data-ng-disabled="!changed">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
|
||||
<h1>Settings</h1>
|
||||
|
||||
<kc-tabs-realm></kc-tabs-realm>
|
||||
|
||||
<ul class="nav nav-tabs nav-tabs-pf">
|
||||
|
@ -98,8 +96,8 @@
|
|||
|
||||
<div class="form-group" data-ng-show="access.manageRealm">
|
||||
<div class="col-md-10 col-md-offset-2">
|
||||
<button kc-save data-ng-show="changed">Save</button>
|
||||
<button kc-reset data-ng-show="changed">Cancel</button>
|
||||
<button kc-save data-ng-disabled="!changed">Save</button>
|
||||
<button kc-reset data-ng-disabled="!changed">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
|
|
@ -5,8 +5,6 @@
|
|||
<li>{{client.clientId}}</li>
|
||||
</ol>
|
||||
|
||||
<h1>{{client.clientId|capitalize}}</h1>
|
||||
|
||||
<kc-tabs-client></kc-tabs-client>
|
||||
|
||||
<form class="form-horizontal" name="clusteringForm" novalidate kc-read-only="!access.manageClients">
|
||||
|
@ -34,8 +32,8 @@
|
|||
|
||||
<div class="form-group">
|
||||
<div class="col-md-10 col-md-offset-2" data-ng-show="access.manageRealm">
|
||||
<button data-kc-save data-ng-show="changed">Save</button>
|
||||
<button data-kc-reset data-ng-show="changed">Cancel</button>
|
||||
<button data-kc-save data-ng-disabled="!changed">Save</button>
|
||||
<button data-kc-reset data-ng-disabled="!changed">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
|
|
@ -5,8 +5,6 @@
|
|||
<li>{{client.clientId}}</li>
|
||||
</ol>
|
||||
|
||||
<h1>{{client.clientId|capitalize}}</h1>
|
||||
|
||||
<kc-tabs-client></kc-tabs-client>
|
||||
|
||||
<form class="form-horizontal" name="credentialForm" novalidate kc-read-only="!access.manageClients">
|
||||
|
|
|
@ -6,10 +6,6 @@
|
|||
<li data-ng-hide="create">{{client.clientId}}</li>
|
||||
</ol>
|
||||
|
||||
<h1 data-ng-show="create">Add Client</h1>
|
||||
<h1 data-ng-hide="create">{{client.clientId|capitalize}}<i id="removeClient" class="pficon pficon-delete clickable" data-ng-show="!create && access.manageClients"
|
||||
data-ng-hide="changed" data-ng-click="remove()"></i></h1>
|
||||
|
||||
<kc-tabs-client></kc-tabs-client>
|
||||
|
||||
<form class="form-horizontal" name="clientForm" novalidate kc-read-only="!access.manageClients">
|
||||
|
@ -274,12 +270,12 @@
|
|||
|
||||
<div class="form-group">
|
||||
<div class="col-md-10 col-md-offset-2" data-ng-show="create && access.manageClients">
|
||||
<button kc-save data-ng-show="changed">Save</button>
|
||||
<button kc-save data-ng-disabled="!changed">Save</button>
|
||||
<button kc-cancel data-ng-click="cancel()">Cancel</button>
|
||||
</div>
|
||||
<div class="col-md-10 col-md-offset-2" data-ng-show="!create && access.manageClients">
|
||||
<button kc-save data-ng-show="changed">Save</button>
|
||||
<button kc-reset data-ng-show="changed">Cancel</button>
|
||||
<button kc-save data-ng-disabled="!changed">Save</button>
|
||||
<button kc-reset data-ng-disabled="!changed">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
|
|
@ -5,8 +5,6 @@
|
|||
<li>{{client.clientId}}</li>
|
||||
</ol>
|
||||
|
||||
<h1>{{client.clientId|capitalize}}</h1>
|
||||
|
||||
<kc-tabs-client></kc-tabs-client>
|
||||
|
||||
<form class="form" name="realmForm" novalidate>
|
||||
|
|
|
@ -5,8 +5,6 @@
|
|||
<li>{{client.clientId}}</li>
|
||||
</ol>
|
||||
|
||||
<h1>{{client.clientId|capitalize}}</h1>
|
||||
|
||||
<kc-tabs-client></kc-tabs-client>
|
||||
|
||||
<form class="form-horizontal" name="keyForm" novalidate kc-read-only="!access.manageRealm">
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<table class="table table-striped table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="kc-table-actions" colspan="3">
|
||||
<th class="kc-table-actions" colspan="4">
|
||||
<div class="form-inline">
|
||||
<div class="form-group">
|
||||
<div class="input-group">
|
||||
|
@ -29,6 +29,7 @@
|
|||
<th>Client ID</th>
|
||||
<th>Enabled</th>
|
||||
<th>Base URL</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
@ -38,6 +39,10 @@
|
|||
<td ng-class="{'text-muted': !client.baseUrl}">
|
||||
<a href="{{client.baseUrl}}" target="_blank" data-ng-show="client.baseUrl">{{client.baseUrl}}</a>
|
||||
<span data-ng-hide="client.baseUrl">Not defined</span>
|
||||
</td>
|
||||
<td class="kc-action-cell">
|
||||
<button class="btn btn-default btn-block btn-sm" kc-open="/realms/{{realm.realm}}/clients/{{client.id}}">Edit</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr data-ng-show="(clients | filter:search).length == 0">
|
||||
<td class="text-muted" colspan="3" data-ng-show="search.clientId">No results</td>
|
||||
|
|
|
@ -22,10 +22,6 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pull-right" data-ng-show="access.manageRealm">
|
||||
<button class="btn btn-primary" data-ng-click="add()">Add Selected</button>
|
||||
</div>
|
||||
</div>
|
||||
</th>
|
||||
</tr>
|
||||
|
@ -48,6 +44,10 @@
|
|||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div data-ng-show="access.manageRealm">
|
||||
<button class="btn btn-primary" data-ng-click="add()">Add Selected</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<kc-menu></kc-menu>
|
|
@ -5,8 +5,6 @@
|
|||
<li>{{client.clientId}}</li>
|
||||
</ol>
|
||||
|
||||
<h1>{{client.clientId|capitalize}}</h1>
|
||||
|
||||
<kc-tabs-client></kc-tabs-client>
|
||||
|
||||
<table class="table table-striped table-bordered">
|
||||
|
|
|
@ -5,8 +5,6 @@
|
|||
<li>{{client.clientId}}</li>
|
||||
</ol>
|
||||
|
||||
<h1>{{client.clientId|capitalize}}</h1>
|
||||
|
||||
<kc-tabs-client></kc-tabs-client>
|
||||
|
||||
<form class="form-horizontal" name="credentialForm" novalidate kc-read-only="!access.manageRealm">
|
||||
|
|
|
@ -43,14 +43,14 @@
|
|||
|
||||
<div class="form-group">
|
||||
<div class="col-md-10 col-md-offset-2" data-ng-show="create && access.manageClients">
|
||||
<button kc-save data-ng-show="changed">Save</button>
|
||||
<button kc-save>Save</button>
|
||||
<button kc-cancel data-ng-click="cancel()">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-md-10 col-md-offset-2" data-ng-show="!create && access.manageClients">
|
||||
<button kc-save data-ng-show="changed">Save</button>
|
||||
<button kc-reset data-ng-show="changed">Cancel</button>
|
||||
<button kc-save data-ng-disabled="!changed">Save</button>
|
||||
<button kc-reset data-ng-disabled="!changed">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -5,14 +5,12 @@
|
|||
<li>{{client.clientId}}</li>
|
||||
</ol>
|
||||
|
||||
<h1>{{client.clientId|capitalize}}</h1>
|
||||
|
||||
<kc-tabs-client></kc-tabs-client>
|
||||
|
||||
<table class="table table-striped table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="kc-table-actions" colspan="3" data-ng-show="access.manageClients">
|
||||
<th class="kc-table-actions" colspan="4" data-ng-show="access.manageClients">
|
||||
<div class="pull-right">
|
||||
<a class="btn btn-default" href="#/create/role/{{realm.realm}}/clients/{{client.id}}">Add Role</a>
|
||||
</div>
|
||||
|
@ -22,6 +20,7 @@
|
|||
<th>Role Name</th>
|
||||
<th>Composite</th>
|
||||
<th>Description</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
@ -29,6 +28,9 @@
|
|||
<td><a href="#/realms/{{realm.realm}}/clients/{{client.id}}/roles/{{role.id}}">{{role.name}}</a></td>
|
||||
<td>{{role.composite}}</td>
|
||||
<td>{{role.description}}</td>
|
||||
<td class="kc-action-cell">
|
||||
<button class="btn btn-default btn-block btn-sm" kc-open="/realms/{{realm.realm}}/clients/{{client.id}}/roles/{{role.id}}">Edit</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr data-ng-show="!roles || roles.length == 0">
|
||||
<td>No client roles available</td>
|
||||
|
|
|
@ -5,8 +5,6 @@
|
|||
<li>{{client.clientId}}</li>
|
||||
</ol>
|
||||
|
||||
<h1>{{client.clientId|capitalize}}</h1>
|
||||
|
||||
<kc-tabs-client></kc-tabs-client>
|
||||
|
||||
<h2><span>{{client.clientId}}</span> Scope Mappings </h2>
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
|
||||
|
||||
<ol class="breadcrumb">
|
||||
<li><a href="#/realms/{{realm.realm}}/clients">Clients</a></li>
|
||||
<li>{{client.clientId}}</li>
|
||||
</ol>
|
||||
|
||||
<h1>{{client.clientId|capitalize}}</h1>
|
||||
|
||||
<kc-tabs-client></kc-tabs-client>
|
||||
|
||||
<h2><span>{{client.clientId}}</span> Service Accounts </h2>
|
||||
<p class="subtitle"></p>
|
||||
<form class="form-horizontal" name="serviceAccountsEnabledForm" novalidate kc-read-only="!access.manageClients">
|
||||
<fieldset class="border-top">
|
||||
<div class="form-group">
|
||||
<label class="col-md-2 control-label" for="serviceAccountsEnabled">Service Accounts Enabled</label>
|
||||
<kc-tooltip>Allows you to authenticate this client to Keycloak and retrieve access token dedicated to this client.</kc-tooltip>
|
||||
<div class="col-md-6">
|
||||
<input ng-model="client.serviceAccountsEnabled" ng-click="serviceAccountsEnabledChanged()" name="serviceAccountsEnabled" id="serviceAccountsEnabled" onoffswitch />
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
|
||||
<kc-menu></kc-menu>
|
|
@ -5,8 +5,6 @@
|
|||
<li>{{client.clientId}}</li>
|
||||
</ol>
|
||||
|
||||
<h1>{{client.clientId|capitalize}}</h1>
|
||||
|
||||
<kc-tabs-client></kc-tabs-client>
|
||||
|
||||
<form class="form-horizontal" name="sessionStats">
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
|
||||
<h1>Settings</h1>
|
||||
|
||||
<kc-tabs-realm></kc-tabs-realm>
|
||||
|
||||
<ul class="nav nav-tabs nav-tabs-pf">
|
||||
|
@ -27,8 +25,8 @@
|
|||
</fieldset>
|
||||
<div class="form-group" data-ng-show="access.manageRealm">
|
||||
<div class="col-md-10 col-md-offset-2">
|
||||
<button kc-save data-ng-show="changed">Save</button>
|
||||
<button kc-reset data-ng-show="changed">Cancel</button>
|
||||
<button kc-save data-ng-disabled="!changed">Save</button>
|
||||
<button kc-reset data-ng-disabled="!changed">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
|
|
@ -5,14 +5,7 @@
|
|||
<li data-ng-show="create">Add User Federation Provider</li>
|
||||
</ol>
|
||||
|
||||
<h1 data-ng-hide="create">{{instance.providerName|capitalize}}<i class="pficon pficon-delete clickable" data-ng-show="!create && access.manageUsers"
|
||||
data-ng-hide="changed" data-ng-click="remove()"></i></h1>
|
||||
<h1 data-ng-show="create">Add {{instance.providerName|capitalize}} User Federation Provide</h1>
|
||||
|
||||
<ul class="nav nav-tabs" data-ng-hide="create">
|
||||
<li class="active"><a href="#/realms/{{realm.realm}}/user-federation/providers/{{instance.providerName}}/{{instance.id}}">Settings</a></li>
|
||||
<li><a href="#/realms/{{realm.realm}}/user-federation/providers/{{instance.providerName}}/{{instance.id}}/mappers">Mappers</a></li>
|
||||
</ul>
|
||||
<kc-tabs-user-federation></kc-tabs-user-federation>
|
||||
|
||||
<form class="form-horizontal" name="realmForm" novalidate kc-read-only="!access.manageRealm">
|
||||
<fieldset>
|
||||
|
@ -87,8 +80,8 @@
|
|||
|
||||
<div class="form-group">
|
||||
<div class="col-md-10 col-md-offset-2" data-ng-show="!create && access.manageUsers">
|
||||
<button kc-save data-ng-show="changed">Save</button>
|
||||
<button kc-reset data-ng-show="changed">Cancel</button>
|
||||
<button kc-save data-ng-disabled="!changed">Save</button>
|
||||
<button kc-reset data-ng-disabled="!changed">Cancel</button>
|
||||
<button class="btn btn-primary" data-ng-click="triggerChangedUsersSync()" data-ng-hide="changed">Synchronize changed users</button>
|
||||
<button class="btn btn-primary" data-ng-click="triggerFullSync()" data-ng-hide="changed">Synchronize all users</button>
|
||||
</div>
|
||||
|
|
|
@ -5,14 +5,7 @@
|
|||
<li data-ng-show="create">Add User Federation Provider</li>
|
||||
</ol>
|
||||
|
||||
<h1 data-ng-hide="create">Kerberos<i class="pficon pficon-delete clickable" data-ng-show="!create && access.manageUsers"
|
||||
data-ng-hide="changed" data-ng-click="remove()"></i></h1>
|
||||
<h1 data-ng-show="create">Add Kerberos User Federation Provider</h1>
|
||||
|
||||
<ul class="nav nav-tabs" data-ng-hide="create">
|
||||
<li class="active"><a href="#/realms/{{realm.realm}}/user-federation/providers/{{instance.providerName}}/{{instance.id}}">Settings</a></li>
|
||||
<li><a href="#/realms/{{realm.realm}}/user-federation/providers/{{instance.providerName}}/{{instance.id}}/mappers">Mappers</a></li>
|
||||
</ul>
|
||||
<kc-tabs-user-federation></kc-tabs-user-federation>
|
||||
|
||||
<form class="form-horizontal" name="realmForm" novalidate kc-read-only="!access.manageRealm">
|
||||
<fieldset>
|
||||
|
@ -106,8 +99,8 @@
|
|||
|
||||
<div class="form-group">
|
||||
<div class="col-md-10 col-md-offset-2" data-ng-show="!create && access.manageUsers">
|
||||
<button kc-save data-ng-show="changed">Save</button>
|
||||
<button kc-reset data-ng-show="changed">Cancel</button>
|
||||
<button kc-save data-ng-disabled="!changed">Save</button>
|
||||
<button kc-reset data-ng-disabled="!changed">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
|
|
@ -5,14 +5,7 @@
|
|||
<li data-ng-show="create">Add User Federation Provider</li>
|
||||
</ol>
|
||||
|
||||
<h1 data-ng-hide="create">LDAP<i class="pficon pficon-delete clickable" data-ng-show="!create && access.manageUsers"
|
||||
data-ng-hide="changed" data-ng-click="remove()"></i></h1>
|
||||
<h1 data-ng-show="create">Add LDAP User Federation Provider</h1>
|
||||
|
||||
<ul class="nav nav-tabs" data-ng-hide="create">
|
||||
<li class="active"><a href="#/realms/{{realm.realm}}/user-federation/providers/{{instance.providerName}}/{{instance.id}}">Settings</a></li>
|
||||
<li><a href="#/realms/{{realm.realm}}/user-federation/providers/{{instance.providerName}}/{{instance.id}}/mappers">Mappers</a></li>
|
||||
</ul>
|
||||
<kc-tabs-user-federation></kc-tabs-user-federation>
|
||||
|
||||
<form class="form-horizontal" name="realmForm" novalidate kc-read-only="!access.manageRealm">
|
||||
|
||||
|
@ -281,8 +274,8 @@
|
|||
|
||||
<div class="form-group">
|
||||
<div class="col-md-10 col-md-offset-2" data-ng-show="!create && access.manageUsers">
|
||||
<button kc-save data-ng-show="changed">Save</button>
|
||||
<button kc-reset data-ng-show="changed">Cancel</button>
|
||||
<button kc-save data-ng-disabled="!changed">Save</button>
|
||||
<button kc-reset data-ng-disabled="!changed">Cancel</button>
|
||||
<button class="btn btn-primary" data-ng-click="triggerChangedUsersSync()" data-ng-hide="changed">Synchronize changed users</button>
|
||||
<button class="btn btn-primary" data-ng-click="triggerFullSync()" data-ng-hide="changed">Synchronize all users</button>
|
||||
</div>
|
||||
|
|
|
@ -48,14 +48,19 @@
|
|||
|
||||
<kc-provider-config config="mapper.config" properties="mapperType.properties" realm="realm" clients="clients"></kc-provider-config>
|
||||
</fieldset>
|
||||
<div class="pull-right form-actions" data-ng-show="create && access.manageRealm">
|
||||
<button kc-cancel data-ng-click="cancel()">Cancel</button>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="col-md-10 col-md-offset-2" data-ng-show="!create && access.manageRealm">
|
||||
<button kc-save>Save</button>
|
||||
<button kc-cancel data-ng-click="cancel()">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pull-right form-actions" data-ng-show="!create && access.manageRealm">
|
||||
<button kc-reset data-ng-show="changed">Clear changes</button>
|
||||
<div class="form-group">
|
||||
<div class="col-md-10 col-md-offset-2" data-ng-show="!create && access.manageRealm">
|
||||
<button kc-save data-ng-show="changed">Save</button>
|
||||
<button kc-reset data-ng-disabled="!changed">Clear changes</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
|
|
@ -5,12 +5,7 @@
|
|||
<li>User Federation Mappers</li>
|
||||
</ol>
|
||||
|
||||
<h1>{{provider.providerName === 'ldap' ? 'LDAP' : (provider.providerName|capitalize)}}</h1>
|
||||
|
||||
<ul class="nav nav-tabs" data-ng-hide="create">
|
||||
<li><a href="#/realms/{{realm.realm}}/user-federation/providers/{{provider.providerName}}/{{provider.id}}">Settings</a></li>
|
||||
<li class="active"><a href="#/realms/{{realm.realm}}/user-federation/providers/{{provider.providerName}}/{{provider.id}}/mappers">Mappers</a></li>
|
||||
</ul>
|
||||
<kc-tabs-user-federation></kc-tabs-user-federation>
|
||||
|
||||
<table class="table table-striped table-bordered">
|
||||
<thead>
|
||||
|
|
|
@ -47,14 +47,19 @@
|
|||
</div>
|
||||
<kc-provider-config config="mapper.config" properties="mapperType.properties" realm="realm"></kc-provider-config>
|
||||
</fieldset>
|
||||
<div class="pull-right form-actions" data-ng-show="create && access.manageRealm">
|
||||
<button kc-cancel data-ng-click="cancel()">Cancel</button>
|
||||
|
||||
<div class="form-group" data-ng-show="create && access.manageRealm">
|
||||
<div class="col-md-10 col-md-offset-2">
|
||||
<button kc-save>Save</button>
|
||||
<button kc-cancel data-ng-click="cancel()">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pull-right form-actions" data-ng-show="!create && access.manageRealm">
|
||||
<button kc-reset data-ng-show="changed">Clear changes</button>
|
||||
<button kc-save data-ng-show="changed">Save</button>
|
||||
<div class="form-group" data-ng-show="!create && access.manageRealm">
|
||||
<div class="col-md-10 col-md-offset-2">
|
||||
<button kc-save data-ng-disabled="!changed">Save</button>
|
||||
<button kc-reset data-ng-disabled="!changed">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
|
|
@ -4,13 +4,7 @@
|
|||
<li>{{identityProvider.alias}}</li>
|
||||
</ol>
|
||||
|
||||
<h1>{{identityProvider.alias|capitalize}}</h1>
|
||||
|
||||
<ul class="nav nav-tabs" data-ng-hide="newIdentityProvider">
|
||||
<li><a href="#/realms/{{realm.realm}}/identity-provider-settings/provider/{{identityProvider.providerId}}/{{identityProvider.alias}}">Settings</a></li>
|
||||
<li class="active"><a href="#/realms/{{realm.realm}}/identity-provider-mappers/{{identityProvider.alias}}/mappers">Mappers</a></li>
|
||||
<li><a href="#/realms/{{realm.realm}}/identity-provider-settings/provider/{{identityProvider.providerId}}/{{identityProvider.alias}}/export" data-ng-show="!importFile && !newIdentityProvider && identityProvider.providerId == 'saml'">Export</a></li>
|
||||
</ul>
|
||||
<kc-tabs-identity-provider></kc-tabs-identity-provider>
|
||||
|
||||
<table class="table table-striped table-bordered">
|
||||
<thead>
|
||||
|
|
|
@ -4,8 +4,6 @@
|
|||
<kc-tabs-authentication></kc-tabs-authentication>
|
||||
|
||||
<form class="form-horizontal" name="realmForm" novalidate kc-read-only="!access.manageRealm">
|
||||
<fieldset class="border-top">
|
||||
<legend><span class="text">Realm Password Policy</span> <kc-tooltip>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.</kc-tooltip></legend>
|
||||
<table class="table table-striped table-bordered">
|
||||
<caption class="hidden">Table of Password Policies</caption>
|
||||
<thead>
|
||||
|
@ -25,7 +23,7 @@
|
|||
<tr>
|
||||
<th>Policy Type</th>
|
||||
<th>Policy Value</th>
|
||||
<th class="actions">Actions</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
@ -35,18 +33,17 @@
|
|||
<input class="form-control" ng-model="p.value" ng-show="p.name != 'notUsername' "
|
||||
placeholder="No value assigned" min="1" required>
|
||||
</td>
|
||||
<td class="actions">
|
||||
<div class="action-div"><i class="pficon pficon-delete" ng-click="removePolicy($index)" tooltip-placement="right" tooltip="Remove Policy"></i></div>
|
||||
<td class="kc-action-cell">
|
||||
<button class="btn btn-default btn-block btn-sm" ng-click="removePolicy($index)">Delete</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</fieldset>
|
||||
|
||||
<div class="form-group" data-ng-show="access.manageRealm">
|
||||
<div class="col-md-10 col-md-offset-2">
|
||||
<button kc-save data-ng-show="changed">Save</button>
|
||||
<button kc-reset data-ng-show="changed">Cancel</button>
|
||||
<div class="col-md-12">
|
||||
<button kc-save data-ng-disabled="!changed">Save</button>
|
||||
<button kc-reset data-ng-disabled="!changed">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
|
|
@ -81,8 +81,8 @@
|
|||
|
||||
<div class="form-group">
|
||||
<div class="col-md-10 col-md-offset-2" data-ng-show="!create && access.manageRealm">
|
||||
<button kc-save data-ng-show="changed">Save</button>
|
||||
<button kc-reset data-ng-show="changed">Cancel</button>
|
||||
<button kc-save data-ng-disabled="!changed">Save</button>
|
||||
<button kc-reset data-ng-disabled="!changed">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
|
||||
<h1>Settings</h1>
|
||||
|
||||
<kc-tabs-realm></kc-tabs-realm>
|
||||
|
||||
<form class="form-horizontal" name="realmForm" novalidate kc-read-only="!access.manageRealm">
|
||||
|
@ -21,8 +19,8 @@
|
|||
|
||||
<div class="form-group" data-ng-show="access.manageRealm">
|
||||
<div class="col-md-10 col-md-offset-2">
|
||||
<button kc-save data-ng-show="changed">Save</button>
|
||||
<button kc-reset data-ng-show="changed">Cancel</button>
|
||||
<button kc-save data-ng-disabled="!changed">Save</button>
|
||||
<button kc-reset data-ng-disabled="!changed">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
|
||||
|
||||
<h1>Add Realm</h1>
|
||||
|
||||
<form class="form-horizontal" name="realmForm" novalidate>
|
||||
<fieldset>
|
||||
<legend><span class="text">Import Realm</span></legend>
|
||||
|
@ -15,10 +12,10 @@
|
|||
<span class="kc-uploaded-file" data-ng-show="files.length > 0">{{files[0].name}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" data-ng-show="files.length > 0">
|
||||
<div class="form-group">
|
||||
<div class="col-md-10 col-md-offset-2">
|
||||
<button type="submit" data-ng-click="uploadFile()" class="btn btn-primary">Upload</button>
|
||||
<button type="submit" data-ng-click="clearFileSelect()" class="btn btn-default">Cancel</button>
|
||||
<button type="submit" data-ng-disabled="files.length == 0" data-ng-click="uploadFile()" class="btn btn-primary">Upload</button>
|
||||
<button type="submit" data-ng-disabled="files.length == 0" data-ng-click="clearFileSelect()" class="btn btn-default">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
@ -44,7 +41,7 @@
|
|||
|
||||
<div class="form-group">
|
||||
<div class="col-md-10 col-md-offset-2">
|
||||
<button kc-save data-ng-show="changed">Save</button>
|
||||
<button kc-save data-ng-disabled="!changed">Create</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
|
||||
<h1 data-ng-hide="createRealm">Settings</h1>
|
||||
<h1 data-ng-show="createRealm">Add Realm</h1>
|
||||
|
||||
<kc-tabs-realm></kc-tabs-realm>
|
||||
|
||||
<form class="form-horizontal " name="realmForm" novalidate kc-read-only="!access.manageRealm">
|
||||
|
@ -27,9 +24,8 @@
|
|||
</div>
|
||||
|
||||
<div class="col-md-10 col-md-offset-2" data-ng-show="!createRealm && access.manageRealm">
|
||||
<button kc-save data-ng-show="changed">Save</button>
|
||||
<button kc-reset data-ng-show="changed">Cancel</button>
|
||||
<button kc-delete data-ng-click="remove()" data-ng-hide="changed">Delete Realm</button>
|
||||
<button kc-save data-ng-disabled="!changed">Save</button>
|
||||
<button kc-reset data-ng-disabled="!changed">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
|
|
@ -4,14 +4,7 @@
|
|||
<li>{{identityProvider.alias}}</li>
|
||||
</ol>
|
||||
|
||||
<h1 data-ng-hide="create">{{identityProvider.alias|capitalize}}</h1>
|
||||
<h1 data-ng-show="create">Add OpenID Connect Identity Provider</h1>
|
||||
|
||||
<ul class="nav nav-tabs" data-ng-hide="newIdentityProvider">
|
||||
<li><a href="#/realms/{{realm.realm}}/identity-provider-settings/provider/{{identityProvider.providerId}}/{{identityProvider.alias}}">Settings</a></li>
|
||||
<li><a href="#/realms/{{realm.realm}}/identity-provider-mappers/{{identityProvider.alias}}/mappers">Mappers</a></li>
|
||||
<li class="active"><a href="#/realms/{{realm.realm}}/identity-provider-settings/provider/{{identityProvider.providerId}}/{{identityProvider.alias}}/export" data-ng-show="!importFile && !newIdentityProvider">Export</a></li>
|
||||
</ul>
|
||||
<kc-tabs-identity-provider></kc-tabs-identity-provider>
|
||||
|
||||
<form class="form-horizontal" name="realmForm" novalidate>
|
||||
<fieldset class="border-top">
|
||||
|
|
|
@ -4,9 +4,7 @@
|
|||
<li>{{identityProvider.alias}}</li>
|
||||
</ol>
|
||||
|
||||
<h1 data-ng-hide="create">{{identityProvider.alias|capitalize}}<i class="pficon pficon-delete clickable"
|
||||
data-ng-hide="newIdentityProvider || changed" data-ng-click="remove()"></i></h1>
|
||||
<h1 data-ng-show="create">Add OpenID Connect Identity Provider</h1>
|
||||
<kc-tabs-identity-provider></kc-tabs-identity-provider>
|
||||
|
||||
<ul class="nav nav-tabs" data-ng-hide="newIdentityProvider">
|
||||
<li class="active"><a href="#/realms/{{realm.realm}}/identity-provider-settings/provider/{{identityProvider.providerId}}/{{identityProvider.alias}}">Settings</a></li>
|
||||
|
@ -217,8 +215,8 @@
|
|||
|
||||
<div class="form-group">
|
||||
<div class="col-md-10 col-md-offset-2">
|
||||
<button kc-save data-ng-show="changed">Save</button>
|
||||
<button kc-cancel data-ng-click="cancel()" data-ng-show="changed">Cancel</button>
|
||||
<button kc-save data-ng-disabled="!changed">Save</button>
|
||||
<button kc-cancel data-ng-click="cancel()" data-ng-disabled="!changed">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
|
|
@ -4,15 +4,7 @@
|
|||
<li>{{identityProvider.alias}}</li>
|
||||
</ol>
|
||||
|
||||
<h1 data-ng-hide="create">{{identityProvider.alias|capitalize}}<i class="pficon pficon-delete clickable"
|
||||
data-ng-hide="newIdentityProvider || changed" data-ng-click="remove()"></i></h1>
|
||||
<h1 data-ng-show="create">Add SAML Identity Provider</h1>
|
||||
|
||||
<ul class="nav nav-tabs" data-ng-hide="newIdentityProvider">
|
||||
<li class="active"><a href="#/realms/{{realm.realm}}/identity-provider-settings/provider/{{identityProvider.providerId}}/{{identityProvider.alias}}">Settings</a></li>
|
||||
<li><a href="#/realms/{{realm.realm}}/identity-provider-mappers/{{identityProvider.alias}}/mappers">Mappers</a></li>
|
||||
<li><a href="#/realms/{{realm.realm}}/identity-provider-settings/provider/{{identityProvider.providerId}}/{{identityProvider.alias}}/export" data-ng-show="!importFile && !newIdentityProvider && identityProvider.providerId == 'saml'">Export</a></li>
|
||||
</ul>
|
||||
<kc-tabs-identity-provider></kc-tabs-identity-provider>
|
||||
|
||||
<form class="form-horizontal" name="realmForm" novalidate>
|
||||
<fieldset>
|
||||
|
@ -196,8 +188,8 @@
|
|||
|
||||
<div class="form-group">
|
||||
<div class="col-md-10 col-md-offset-2">
|
||||
<button kc-save data-ng-show="changed">Save</button>
|
||||
<button kc-cancel data-ng-click="cancel()" data-ng-show="changed">Cancel</button>
|
||||
<button kc-save data-ng-disabled="!changed">Save</button>
|
||||
<button kc-cancel data-ng-click="cancel()" data-ng-disabled="!changed">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
|
|
@ -4,14 +4,7 @@
|
|||
<li>{{identityProvider.alias}}</li>
|
||||
</ol>
|
||||
|
||||
<h1 data-ng-hide="create">{{identityProvider.alias|capitalize}}<i class="pficon pficon-delete clickable"
|
||||
data-ng-hide="newIdentityProvider || changed" data-ng-click="remove()"></i></h1>
|
||||
<h1 data-ng-show="create">Add Social Identity Provider</h1>
|
||||
|
||||
<ul class="nav nav-tabs" data-ng-hide="newIdentityProvider">
|
||||
<li class="active"><a href="#/realms/{{realm.realm}}/identity-provider-settings/provider/{{identityProvider.providerId}}/{{identityProvider.alias}}">Settings</a></li>
|
||||
<li><a href="#/realms/{{realm.realm}}/identity-provider-mappers/{{identityProvider.alias}}/mappers">Mappers</a></li>
|
||||
</ul>
|
||||
<kc-tabs-identity-provider></kc-tabs-identity-provider>
|
||||
|
||||
<form class="form-horizontal" name="realmForm" novalidate>
|
||||
<fieldset>
|
||||
|
@ -108,8 +101,8 @@
|
|||
|
||||
<div class="form-group">
|
||||
<div class="col-md-10 col-md-offset-2">
|
||||
<button kc-save data-ng-show="changed">Save</button>
|
||||
<button kc-cancel data-ng-click="cancel()" data-ng-show="changed">Cancel</button>
|
||||
<button kc-save data-ng-disabled="!changed">Save</button>
|
||||
<button kc-cancel data-ng-click="cancel()" data-ng-disabled="!changed">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
<caption class="hidden">Table of identity providers</caption>
|
||||
<thead>
|
||||
<tr>
|
||||
<th colspan="4" class="kc-table-actions">
|
||||
<th colspan="5" class="kc-table-actions">
|
||||
<div class="dropdown pull-right">
|
||||
<select class="form-control" ng-model="provider"
|
||||
ng-options="p.name group by p.groupName for p in allProviders track by p.id"
|
||||
|
@ -23,6 +23,7 @@
|
|||
<th>Provider</th>
|
||||
<th>Enabled</th>
|
||||
<th width="15%">GUI order</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody ng-show="configuredProviders.length > 0">
|
||||
|
@ -33,6 +34,9 @@
|
|||
<td>{{identityProvider.providerId}}</td>
|
||||
<td>{{identityProvider.enabled}}</td>
|
||||
<td>{{identityProvider.config.guiOrder}}</td>
|
||||
<td class="kc-action-cell">
|
||||
<button class="btn btn-default btn-block btn-sm" kc-open="/realms/{{realm.realm}}/identity-provider-settings/provider/{{identityProvider.providerId}}/{{identityProvider.alias}}">Edit</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
|
||||
<h1>Settings</h1>
|
||||
|
||||
<kc-tabs-realm></kc-tabs-realm>
|
||||
|
||||
<form class="form-horizontal" name="realmForm" novalidate kc-read-only="!access.manageRealm">
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
|
||||
<h1>Settings</h1>
|
||||
|
||||
<kc-tabs-realm></kc-tabs-realm>
|
||||
|
||||
<form class="form-horizontal" name="realmForm" novalidate kc-read-only="!access.manageRealm">
|
||||
|
@ -64,8 +62,8 @@
|
|||
|
||||
<div class="form-group" data-ng-show="access.manageRealm">
|
||||
<div class="col-md-10 col-md-offset-2">
|
||||
<button kc-save data-ng-show="changed">Save</button>
|
||||
<button kc-reset data-ng-show="changed">Cancel</button>
|
||||
<button kc-save data-ng-disabled="!changed">Save</button>
|
||||
<button kc-reset data-ng-disabled="!changed">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
|
||||
<h1>Settings</h1>
|
||||
|
||||
<kc-tabs-realm></kc-tabs-realm>
|
||||
|
||||
<form class="form-horizontal" name="realmForm" novalidate kc-read-only="!access.manageRealm">
|
||||
|
@ -56,8 +54,8 @@
|
|||
|
||||
<div class="form-group" data-ng-show="access.manageRealm">
|
||||
<div class="col-md-10 col-md-offset-2">
|
||||
<button data-kc-save data-ng-show="changed">Save</button>
|
||||
<button data-kc-reset data-ng-show="changed">Cancel</button>
|
||||
<button data-kc-save data-ng-disabled="!changed">Save</button>
|
||||
<button data-kc-reset data-ng-disabled="!changed">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
|
||||
<h1>Settings</h1>
|
||||
|
||||
<kc-tabs-realm></kc-tabs-realm>
|
||||
|
||||
<form class="form-horizontal" name="realmForm" novalidate kc-read-only="!access.manageRealm">
|
||||
|
@ -88,8 +86,8 @@
|
|||
|
||||
<div class="form-group" data-ng-show="access.manageRealm">
|
||||
<div class="col-md-10 col-md-offset-2">
|
||||
<button kc-save data-ng-show="changed">Save</button>
|
||||
<button kc-reset data-ng-show="changed">Cancel</button>
|
||||
<button kc-save data-ng-disabled="!changed">Save</button>
|
||||
<button kc-reset data-ng-disabled="!changed">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
|
||||
<h1>Settings</h1>
|
||||
|
||||
<kc-tabs-realm></kc-tabs-realm>
|
||||
|
||||
<form class="form-horizontal" name="realmForm" novalidate kc-read-only="!access.manageRealm">
|
||||
|
@ -116,8 +114,8 @@
|
|||
|
||||
<div class="form-group">
|
||||
<div class="col-md-10 col-md-offset-2" data-ng-show="access.manageRealm">
|
||||
<button kc-save data-ng-show="changed">Save</button>
|
||||
<button kc-reset data-ng-show="changed">Cancel</button>
|
||||
<button kc-save data-ng-disabled="!changed">Save</button>
|
||||
<button kc-reset data-ng-disabled="!changed">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
|
|
@ -46,8 +46,8 @@
|
|||
|
||||
<div class="form-group">
|
||||
<div class="col-md-10 col-md-offset-2" data-ng-show="!create && access.manageRealm">
|
||||
<button kc-save data-ng-show="changed">Save</button>
|
||||
<button kc-reset data-ng-show="changed">Cancel</button>
|
||||
<button kc-save data-ng-disabled="!changed">Save</button>
|
||||
<button kc-reset data-ng-disabled="!changed">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
<table class="table table-striped table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="kc-table-actions" colspan="3">
|
||||
<th class="kc-table-actions" colspan="4">
|
||||
<div class="form-inline">
|
||||
<div class="form-group">
|
||||
<div class="input-group">
|
||||
|
@ -30,6 +30,7 @@
|
|||
<th>Role Name</th>
|
||||
<th>Composite</th>
|
||||
<th>Description</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
@ -37,6 +38,9 @@
|
|||
<td><a href="#/realms/{{realm.realm}}/roles/{{role.id}}">{{role.name}}</a></td>
|
||||
<td>{{role.composite}}</td>
|
||||
<td>{{role.description}}</td>
|
||||
<td class="kc-action-cell">
|
||||
<button class="btn btn-default btn-block btn-sm" kc-open="/realms/{{realm.realm}}/roles/{{role.id}}">Edit</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr data-ng-show="(roles | filter:{name: searchQuery}).length == 0">
|
||||
<td class="text-muted" colspan="3" data-ng-show="searchQuery">No results</td>
|
||||
|
|
|
@ -4,8 +4,6 @@
|
|||
<li>{{user.username}}</li>
|
||||
</ol>
|
||||
|
||||
<h1>{{user.username|capitalize}}</h1>
|
||||
|
||||
<kc-tabs-user></kc-tabs-user>
|
||||
|
||||
<form class="form-horizontal" name="realmForm" novalidate>
|
||||
|
|
|
@ -4,8 +4,6 @@
|
|||
<li>{{user.username}}</li>
|
||||
</ol>
|
||||
|
||||
<h1>{{user.username|capitalize}}</h1>
|
||||
|
||||
<kc-tabs-user></kc-tabs-user>
|
||||
|
||||
<table class="table table-striped table-bordered">
|
||||
|
@ -37,9 +35,9 @@
|
|||
</span>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<button class="btn btn-danger" ng-click="revokeConsent(consent.clientId)">
|
||||
<i class="pficon pficon-delete"></i>
|
||||
<td class="kc-action-cell">
|
||||
<button class="btn btn-default btn-block btn-sm" ng-click="revokeConsent(consent.clientId)">
|
||||
<i class="pficon pficon-delete"></i> Revoke
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
|
|
|
@ -4,8 +4,6 @@
|
|||
<li>{{user.username}}</li>
|
||||
</ol>
|
||||
|
||||
<h1>{{user.username|capitalize}}</h1>
|
||||
|
||||
<kc-tabs-user></kc-tabs-user>
|
||||
|
||||
<form class="form-horizontal" name="userForm" novalidate>
|
||||
|
|
|
@ -5,10 +5,6 @@
|
|||
<li data-ng-show="create">Add User</li>
|
||||
</ol>
|
||||
|
||||
<h1 data-ng-hide="create">{{user.username|capitalize}}<i id="removeUser" class="pficon pficon-delete clickable" data-ng-show="!create && access.manageUsers && !changed"
|
||||
data-ng-click="remove()"></i></h1>
|
||||
<h1 data-ng-show="create">Add User</h1>
|
||||
|
||||
<kc-tabs-user></kc-tabs-user>
|
||||
|
||||
<form class="form-horizontal" name="userForm" novalidate kc-read-only="!access.manageUsers">
|
||||
|
@ -126,9 +122,9 @@
|
|||
<button kc-cancel data-ng-click="cancel()">Cancel</button>
|
||||
</div>
|
||||
|
||||
<div class="col-md-10 col-md-offset-2" data-ng-show="!create">
|
||||
<button kc-save data-ng-show="access.manageUsers && changed">Save</button>
|
||||
<button kc-reset data-ng-show="access.manageUsers && changed">Cancel</button>
|
||||
<div class="col-md-10 col-md-offset-2" data-ng-show="!create && access.manageUsers">
|
||||
<button kc-save data-ng-disabled="!changed">Save</button>
|
||||
<button kc-reset data-ng-disabled="!changed">Cancel</button>
|
||||
<button data-ng-show="access.impersonation" class="btn btn-default" data-ng-click="impersonate()" tooltip="Login as this user. If user is in same realm as you, your current login session will be logged out before you are logged in as this user.">Impersonate</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -39,9 +39,12 @@
|
|||
|
||||
</fieldset>
|
||||
|
||||
<div class="pull-right form-actions" data-ng-show="access.manageRealm">
|
||||
<button kc-cancel data-ng-click="cancel()">Cancel</button>
|
||||
|
||||
<div class="form-group" data-ng-show="access.manageRealm">
|
||||
<div class="col-md-10 col-md-offset-2">
|
||||
<button kc-save>Save</button>
|
||||
<button kc-cancel data-ng-click="cancel()">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
|
|
|
@ -4,16 +4,14 @@
|
|||
<li>{{user.username}}</li>
|
||||
</ol>
|
||||
|
||||
<h1>{{user.username|capitalize}}</h1>
|
||||
|
||||
<kc-tabs-user></kc-tabs-user>
|
||||
|
||||
<table class="table table-striped table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<tr data-ng-show="hasAnyProvidersToCreate()">
|
||||
<th class="kc-table-actions" colspan="4">
|
||||
<div class="form-inline">
|
||||
<div class="pull-right" data-ng-show="hasAnyProvidersToCreate()">
|
||||
<div class="pull-right">
|
||||
<a class="btn btn-primary" href="#/create/federated-identity/{{realm.realm}}/{{user.id}}">Create</a>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -31,8 +29,8 @@
|
|||
<td>{{identity.identityProvider}}</td>
|
||||
<td>{{identity.userId}}</td>
|
||||
<td>{{identity.userName}}</td>
|
||||
<td class="actions">
|
||||
<div class="action-div"><i class="pficon pficon-delete" ng-click="removeProviderLink(identity)" tooltip-placement="right" tooltip="Remove Provider Link"></i></div>
|
||||
<td class="kc-action-cell">
|
||||
<button class="btn btn-default btn-block btn-sm" ng-click="removeProviderLink(identity)">Remove</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr data-ng-show="federatedIdentities.length == 0">
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<table class="table table-striped table-bordered">
|
||||
<thead>
|
||||
<tr ng-show="providers.length > 0 && access.manageUsers">
|
||||
<th colspan="3" class="kc-table-actions">
|
||||
<th colspan="4" class="kc-table-actions">
|
||||
<div class="pull-right">
|
||||
<div>
|
||||
<select class="form-control" ng-model="selectedProvider"
|
||||
|
@ -22,6 +22,7 @@
|
|||
<th>ID</th>
|
||||
<th>Provider Name</th>
|
||||
<th>Priority</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
@ -29,6 +30,9 @@
|
|||
<td><a href="#/realms/{{realm.realm}}/user-federation/providers/{{instance.providerName}}/{{instance.id}}">{{instance.displayName}}</a></td>
|
||||
<td>{{instance.providerName|capitalize}}</td>
|
||||
<td>{{instance.priority}}</td>
|
||||
<td class="kc-action-cell">
|
||||
<button class="btn btn-default btn-block btn-sm" kc-open="/realms/{{realm.realm}}/user-federation/providers/{{instance.providerName}}/{{instance.id}}">Edit</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr data-ng-show="!instances || instances.length == 0">
|
||||
<td class="text-muted">No user federation providers configured</td>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<caption data-ng-show="users" class="hidden">Table of realm users</caption>
|
||||
<thead>
|
||||
<tr>
|
||||
<th colspan="{{access.impersonation == true ? '5' : '4'}}">
|
||||
<th colspan="{{access.impersonation == true ? '6' : '5'}}">
|
||||
<div class="form-inline">
|
||||
<div class="form-group">
|
||||
<div class="input-group">
|
||||
|
@ -30,7 +30,7 @@
|
|||
<th>Last Name</th>
|
||||
<th>First Name</th>
|
||||
<th>Email</th>
|
||||
<th data-ng-show="access.impersonation"></th>
|
||||
<th colspan="{{access.impersonation == true ? '2' : '1'}}">Actions</th>
|
||||
</tr>
|
||||
</tr>
|
||||
</thead>
|
||||
|
@ -51,7 +51,12 @@
|
|||
<td>{{user.lastName}}</td>
|
||||
<td>{{user.firstName}}</td>
|
||||
<td>{{user.email}}</td>
|
||||
<td data-ng-show="access.impersonation"><button class="btn btn-default" data-ng-click="impersonate(user.id)" tooltip="Login as this user. If user is in same realm as you, your current login session will be logged out before you are logged in as this user.">Impersonate</button></td>
|
||||
<td class="kc-action-cell">
|
||||
<button class="btn btn-default btn-block btn-sm" kc-open="/realms/{{realm.realm}}/users/{{user.id}}">Edit</button>
|
||||
</td>
|
||||
<td data-ng-show="access.impersonation" class="kc-action-cell">
|
||||
<button class="btn btn-default btn-block btn-sm" data-ng-click="impersonate(user.id)" tooltip="Login as this user. If user is in same realm as you, your current login session will be logged out before you are logged in as this user.">Impersonate</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr data-ng-show="!users || users.length == 0">
|
||||
<td class="text-muted" data-ng-show="!users">Please enter a search, or click on view all users</td>
|
||||
|
|
|
@ -4,8 +4,6 @@
|
|||
<li>{{user.username}}</li>
|
||||
</ol>
|
||||
|
||||
<h1>{{user.username|capitalize}}</h1>
|
||||
|
||||
<kc-tabs-user></kc-tabs-user>
|
||||
|
||||
<table class="table table-striped table-bordered">
|
||||
|
@ -36,7 +34,9 @@
|
|||
</div>
|
||||
</ul>
|
||||
</td>
|
||||
<td data-ng-show="access.manageUsers"><a href="" ng-click="logoutSession(session.id)">logout</a> </td>
|
||||
<td class="kc-action-cell" data-ng-show="access.manageUsers">
|
||||
<button class="btn btn-default btn-block btn-sm" ng-click="logoutSession(session.id)">Logout</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
|| path[2] == 'cache-settings'
|
||||
|| path[2] == 'defense'
|
||||
|| path[2] == 'keys-settings' || path[2] == 'smtp-settings' || path[2] == 'ldap-settings' || path[2] == 'auth-settings') && path[3] != 'clients') && 'active'">
|
||||
<a href="#/realms/{{realm.realm}}"><span class="pficon pficon-settings"></span> Settings</a>
|
||||
<a href="#/realms/{{realm.realm}}"><span class="pficon pficon-settings"></span> Realm Settings</a>
|
||||
</li>
|
||||
<li data-ng-show="access.viewClients" data-ng-class="(path[2] == 'clients' || path[1] == 'client' || path[3] == 'clients') && 'active'"><a href="#/realms/{{realm.realm}}/clients"><i class="fa fa-cubes"></i> Clients</a></li>
|
||||
<li data-ng-show="access.viewRealm" data-ng-class="(path[2] == 'roles' || path[2] == 'default-roles' || (path[1] == 'role' && path[3] != 'clients')) && 'active'"><a href="#/realms/{{realm.realm}}/roles"><i class="fa fa-tasks"></i> Roles</a></li>
|
||||
|
|
|
@ -1,4 +1,12 @@
|
|||
<ul class="nav nav-tabs nav-tabs-pf" data-ng-hide="create && !path[4]">
|
||||
<div data-ng-controller="ClientTabCtrl">
|
||||
|
||||
<h1 data-ng-show="create">Add Client</h1>
|
||||
<h1 data-ng-hide="create">
|
||||
{{client.clientId|capitalize}}
|
||||
<i id="removeClient" class="pficon pficon-delete clickable" data-ng-show="access.manageClients" data-ng-click="removeClient()"></i>
|
||||
</h1>
|
||||
|
||||
<ul class="nav nav-tabs nav-tabs-pf" data-ng-hide="create && !path[4]">
|
||||
<li ng-class="{active: !path[4]}"><a href="#/realms/{{realm.realm}}/clients/{{client.id}}">Settings</a></li>
|
||||
<li ng-class="{active: path[4] == 'credentials'}" data-ng-show="!client.publicClient && client.protocol != 'saml'"><a href="#/realms/{{realm.realm}}/clients/{{client.id}}/credentials">Credentials</a></li>
|
||||
<li ng-class="{active: path[4] == 'saml'}" data-ng-show="client.protocol == 'saml' && (client.attributes['saml.client.signature'] == 'true' || client.attributes['saml.encrypt'] == 'true')"><a href="#/realms/{{realm.realm}}/clients/{{client.id}}/saml/keys">SAML Keys</a></li>
|
||||
|
@ -12,7 +20,7 @@
|
|||
<kc-tooltip>Scope mappings allow you to restrict which user role mappings are included within the access token requested by the client.</kc-tooltip>
|
||||
</li>
|
||||
<li ng-class="{active: path[4] == 'revocation'}"><a href="#/realms/{{realm.realm}}/clients/{{client.id}}/revocation">Revocation</a></li>
|
||||
<!-- <li ng-class="{active: path[4] == 'identity-provider'}" data-ng-show="realm.identityFederationEnabled"><a href="#/realms/{{realm.realm}}/clients/{{client.id}}/identity-provider">Identity Provider</a></li> -->
|
||||
<!-- <li ng-class="{active: path[4] == 'identity-provider'}" data-ng-show="realm.identityFederationEnabled"><a href="#/realms/{{realm.realm}}/clients/{{client.id}}/identity-provider">Identity Provider</a></li> -->
|
||||
<li ng-class="{active: path[4] == 'sessions'}" data-ng-show="!client.bearerOnly">
|
||||
<a href="#/realms/{{realm.realm}}/clients/{{client.id}}/sessions">Sessions</a>
|
||||
<kc-tooltip>View active sessions for this client. Allows you to see which users are active and when they logged in.</kc-tooltip>
|
||||
|
@ -25,4 +33,9 @@
|
|||
<kc-tooltip>Helper utility for generating various client adapter configuration formats which you can download or cut and paste to configure your clients.</kc-tooltip>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
<li ng-class="{active: path[4] == 'service-accounts'}" data-ng-show="!client.publicClient && !client.bearerOnly">
|
||||
<a href="#/realms/{{realm.realm}}/clients/{{client.id}}/service-accounts">Service Accounts</a>
|
||||
<kc-tooltip>Allows you to authenticate this client to Keycloak and retrieve access tokens dedicated to this client.</kc-tooltip>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
|
@ -0,0 +1,13 @@
|
|||
<div data-ng-controller="IdentityProviderTabCtrl">
|
||||
<h1 data-ng-hide="path[0] == 'create'">
|
||||
{{identityProvider.alias|capitalize}}
|
||||
<i class="pficon pficon-delete clickable" data-ng-hide="newIdentityProvider || changed" data-ng-click="removeIdentityProvider()"></i>
|
||||
</h1>
|
||||
<h1 data-ng-show="path[0] == 'create'">Add Identity Provider</h1>
|
||||
|
||||
<ul class="nav nav-tabs" data-ng-hide="newIdentityProvider">
|
||||
<li ng-class="{active: !path[6] && path.length > 5}"><a href="#/realms/{{realm.realm}}/identity-provider-settings/provider/{{identityProvider.providerId}}/{{identityProvider.alias}}">Settings</a></li>
|
||||
<li ng-class="{active: path[4] == 'mappers'}"><a href="#/realms/{{realm.realm}}/identity-provider-mappers/{{identityProvider.alias}}/mappers">Mappers</a></li>
|
||||
<li ng-class="{active: path[6] == 'export'}"><a href="#/realms/{{realm.realm}}/identity-provider-settings/provider/{{identityProvider.providerId}}/{{identityProvider.alias}}/export" data-ng-show="!importFile && !newIdentityProvider && identityProvider.providerId == 'saml'">Export</a></li>
|
||||
</ul>
|
||||
</div>
|
|
@ -1,4 +1,11 @@
|
|||
<ul class="nav nav-tabs">
|
||||
<div data-ng-controller="RealmTabCtrl">
|
||||
<h1 data-ng-hide="createRealm">
|
||||
{{realm.realm|capitalize}}
|
||||
<i id="removeRealm" class="pficon pficon-delete clickable" data-ng-show="access.manageRealm" data-ng-click="removeRealm()"></i>
|
||||
</h1>
|
||||
<h1 data-ng-show="createRealm">Add Realm</h1>
|
||||
|
||||
<ul class="nav nav-tabs">
|
||||
<li ng-class="{active: !path[2]}"><a href="#/realms/{{realm.realm}}">General</a></li>
|
||||
<li ng-class="{active: path[2] == 'login-settings'}" data-ng-show="access.viewRealm"><a href="#/realms/{{realm.realm}}/login-settings">Login</a></li>
|
||||
<li ng-class="{active: path[2] == 'keys-settings'}" data-ng-show="access.viewRealm"><a href="#/realms/{{realm.realm}}/keys-settings">Keys</a></li>
|
||||
|
@ -7,4 +14,5 @@
|
|||
<li ng-class="{active: path[2] == 'cache-settings'}" data-ng-show="access.viewRealm"><a href="#/realms/{{realm.realm}}/cache-settings">Cache</a></li>
|
||||
<li ng-class="{active: path[2] == 'token-settings'}" data-ng-show="access.viewRealm"><a href="#/realms/{{realm.realm}}/token-settings">Tokens</a></li>
|
||||
<li ng-class="{active: path[2] == 'defense'}" data-ng-show="access.viewRealm"><a href="#/realms/{{realm.realm}}/defense/headers">Security Defenses</a></li>
|
||||
</ul>
|
||||
</ul>
|
||||
</div>
|
|
@ -0,0 +1,12 @@
|
|||
<div data-ng-controller="UserFederationTabCtrl">
|
||||
<h1 data-ng-hide="create">
|
||||
{{instance.displayName|capitalize}}
|
||||
<i class="pficon pficon-delete clickable" data-ng-show="!create && access.manageUsers" data-ng-click="removeUserFederation()"></i>
|
||||
</h1>
|
||||
<h1 data-ng-show="create">Add User Federation Provider</h1>
|
||||
|
||||
<ul class="nav nav-tabs" data-ng-hide="create">
|
||||
<li ng-class="{active: !path[6]}"><a href="#/realms/{{realm.realm}}/user-federation/providers/{{instance.providerName}}/{{instance.id}}">Settings</a></li>
|
||||
<li ng-class="{active: path[6] == 'mappers'}"><a href="#/realms/{{realm.realm}}/user-federation/providers/{{instance.providerName}}/{{instance.id}}/mappers">Mappers</a></li>
|
||||
</ul>
|
||||
</div>
|
|
@ -1,8 +1,16 @@
|
|||
<ul class="nav nav-tabs" data-ng-show="!create">
|
||||
<div data-ng-controller="UserTabCtrl">
|
||||
<h1 data-ng-hide="create">
|
||||
{{user.username|capitalize}}
|
||||
<i id="removeUser" class="pficon pficon-delete clickable" data-ng-show="!create && access.manageUsers" data-ng-click="removeUser()"></i>
|
||||
</h1>
|
||||
<h1 data-ng-show="create">Add User</h1>
|
||||
|
||||
<ul class="nav nav-tabs" data-ng-show="!create">
|
||||
<li ng-class="{active: !path[4] && path[0] != 'create'}"><a href="#/realms/{{realm.realm}}/users/{{user.id}}">Attributes</a></li>
|
||||
<li ng-class="{active: path[4] == 'user-credentials'}" data-ng-show="access.manageUsers"><a href="#/realms/{{realm.realm}}/users/{{user.id}}/user-credentials">Credentials</a></li>
|
||||
<li ng-class="{active: path[4] == 'role-mappings'}" ><a href="#/realms/{{realm.realm}}/users/{{user.id}}/role-mappings">Role Mappings</a></li>
|
||||
<li ng-class="{active: path[4] == 'consents'}"><a href="#/realms/{{realm.realm}}/users/{{user.id}}/consents">Consents</a></li>
|
||||
<li ng-class="{active: path[4] == 'sessions'}" ><a href="#/realms/{{realm.realm}}/users/{{user.id}}/sessions">Sessions</a></li>
|
||||
<li ng-class="{active: path[4] == 'federated-identity' || path[1] == 'federated-identity'}" data-ng-show="user.federatedIdentities != null"><a href="#/realms/{{realm.realm}}/users/{{user.id}}/federated-identity">Identity Provider Links</a></li>
|
||||
</ul>
|
||||
</ul>
|
||||
</div>
|
|
@ -299,3 +299,17 @@ h1 i {
|
|||
margin-left: 10px;
|
||||
}
|
||||
|
||||
/* Action cell */
|
||||
.kc-action-cell {
|
||||
position: relative;
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
.kc-action-cell .btn {
|
||||
border: none;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
}
|
|
@ -103,6 +103,9 @@ public interface ClientModel extends RoleContainerModel {
|
|||
boolean isConsentRequired();
|
||||
void setConsentRequired(boolean consentRequired);
|
||||
|
||||
boolean isServiceAccountsEnabled();
|
||||
void setServiceAccountsEnabled(boolean serviceAccountsEnabled);
|
||||
|
||||
Set<RoleModel> getScopeMappings();
|
||||
void addScopeMapping(RoleModel role);
|
||||
void deleteScopeMapping(RoleModel role);
|
||||
|
|
|
@ -304,6 +304,11 @@ public class UserFederationManager implements UserProvider {
|
|||
}, realm, firstResult, maxResults);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<UserModel> searchForUserByUserAttributes(Map<String, String> attributes, RealmModel realm) {
|
||||
return session.userStorage().searchForUserByUserAttributes(attributes, realm);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<FederatedIdentityModel> getFederatedIdentities(UserModel user, RealmModel realm) {
|
||||
validateUser(realm, user);
|
||||
|
|
|
@ -32,6 +32,10 @@ public interface UserProvider extends Provider {
|
|||
List<UserModel> searchForUser(String search, RealmModel realm, int firstResult, int maxResults);
|
||||
List<UserModel> searchForUserByAttributes(Map<String, String> attributes, RealmModel realm);
|
||||
List<UserModel> searchForUserByAttributes(Map<String, String> attributes, RealmModel realm, int firstResult, int maxResults);
|
||||
|
||||
// Searching by UserModel.attribute (not property)
|
||||
List<UserModel> searchForUserByUserAttributes(Map<String, String> attributes, RealmModel realm);
|
||||
|
||||
Set<FederatedIdentityModel> getFederatedIdentities(UserModel user, RealmModel realm);
|
||||
FederatedIdentityModel getFederatedIdentity(UserModel user, String socialProvider, RealmModel realm);
|
||||
|
||||
|
|
|
@ -26,6 +26,7 @@ public class ClientEntity extends AbstractIdentifiableEntity {
|
|||
private String baseUrl;
|
||||
private boolean bearerOnly;
|
||||
private boolean consentRequired;
|
||||
private boolean serviceAccountsEnabled;
|
||||
private boolean directGrantsOnly;
|
||||
private int nodeReRegistrationTimeout;
|
||||
|
||||
|
@ -210,6 +211,14 @@ public class ClientEntity extends AbstractIdentifiableEntity {
|
|||
this.consentRequired = consentRequired;
|
||||
}
|
||||
|
||||
public boolean isServiceAccountsEnabled() {
|
||||
return serviceAccountsEnabled;
|
||||
}
|
||||
|
||||
public void setServiceAccountsEnabled(boolean serviceAccountsEnabled) {
|
||||
this.serviceAccountsEnabled = serviceAccountsEnabled;
|
||||
}
|
||||
|
||||
public boolean isDirectGrantsOnly() {
|
||||
return directGrantsOnly;
|
||||
}
|
||||
|
|
|
@ -351,6 +351,6 @@ public final class KeycloakModelUtils {
|
|||
}
|
||||
|
||||
public static String toLowerCaseSafe(String str) {
|
||||
return str==null ? str : str.toLowerCase();
|
||||
return str==null ? null : str.toLowerCase();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -289,6 +289,7 @@ public class ModelToRepresentation {
|
|||
rep.setFullScopeAllowed(clientModel.isFullScopeAllowed());
|
||||
rep.setBearerOnly(clientModel.isBearerOnly());
|
||||
rep.setConsentRequired(clientModel.isConsentRequired());
|
||||
rep.setServiceAccountsEnabled(clientModel.isServiceAccountsEnabled());
|
||||
rep.setDirectGrantsOnly(clientModel.isDirectGrantsOnly());
|
||||
rep.setSurrogateAuthRequired(clientModel.isSurrogateAuthRequired());
|
||||
rep.setBaseUrl(clientModel.getBaseUrl());
|
||||
|
|
|
@ -625,6 +625,7 @@ public class RepresentationToModel {
|
|||
if (resourceRep.getBaseUrl() != null) client.setBaseUrl(resourceRep.getBaseUrl());
|
||||
if (resourceRep.isBearerOnly() != null) client.setBearerOnly(resourceRep.isBearerOnly());
|
||||
if (resourceRep.isConsentRequired() != null) client.setConsentRequired(resourceRep.isConsentRequired());
|
||||
if (resourceRep.isServiceAccountsEnabled() != null) client.setServiceAccountsEnabled(resourceRep.isServiceAccountsEnabled());
|
||||
if (resourceRep.isDirectGrantsOnly() != null) client.setDirectGrantsOnly(resourceRep.isDirectGrantsOnly());
|
||||
if (resourceRep.isPublicClient() != null) client.setPublicClient(resourceRep.isPublicClient());
|
||||
if (resourceRep.isFrontchannelLogout() != null) client.setFrontchannelLogout(resourceRep.isFrontchannelLogout());
|
||||
|
@ -714,6 +715,7 @@ public class RepresentationToModel {
|
|||
if (rep.isEnabled() != null) resource.setEnabled(rep.isEnabled());
|
||||
if (rep.isBearerOnly() != null) resource.setBearerOnly(rep.isBearerOnly());
|
||||
if (rep.isConsentRequired() != null) resource.setConsentRequired(rep.isConsentRequired());
|
||||
if (rep.isServiceAccountsEnabled() != null) resource.setServiceAccountsEnabled(rep.isServiceAccountsEnabled());
|
||||
if (rep.isDirectGrantsOnly() != null) resource.setDirectGrantsOnly(rep.isDirectGrantsOnly());
|
||||
if (rep.isPublicClient() != null) resource.setPublicClient(rep.isPublicClient());
|
||||
if (rep.isFullScopeAllowed() != null) resource.setFullScopeAllowed(rep.isFullScopeAllowed());
|
||||
|
|
|
@ -38,6 +38,7 @@ import org.keycloak.models.utils.CredentialValidation;
|
|||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
|
@ -225,6 +226,25 @@ public class FileUserProvider implements UserProvider {
|
|||
return sortedSubList(found, firstResult, maxResults);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<UserModel> searchForUserByUserAttributes(Map<String, String> attributes, RealmModel realm) {
|
||||
Collection<UserModel> users = inMemoryModel.getUsers(realm.getId());
|
||||
|
||||
for (Map.Entry<String, String> entry : attributes.entrySet()) {
|
||||
|
||||
List<UserModel> matchedUsers = new ArrayList<>();
|
||||
for (UserModel user : users) {
|
||||
List<String> vals = user.getAttribute(entry.getKey());
|
||||
if (vals.contains(entry.getValue())) {
|
||||
matchedUsers.add(user);
|
||||
}
|
||||
}
|
||||
users = matchedUsers;
|
||||
}
|
||||
|
||||
return (List<UserModel>) users;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<FederatedIdentityModel> getFederatedIdentities(UserModel userModel, RealmModel realm) {
|
||||
UserEntity userEntity = ((UserAdapter)getUserById(userModel.getId(), realm)).getUserEntity();
|
||||
|
|
|
@ -441,6 +441,16 @@ public class ClientAdapter implements ClientModel {
|
|||
entity.setConsentRequired(consentRequired);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isServiceAccountsEnabled() {
|
||||
return entity.isServiceAccountsEnabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setServiceAccountsEnabled(boolean serviceAccountsEnabled) {
|
||||
entity.setServiceAccountsEnabled(serviceAccountsEnabled);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDirectGrantsOnly() {
|
||||
return entity.isDirectGrantsOnly();
|
||||
|
|
|
@ -412,6 +412,18 @@ public class ClientAdapter implements ClientModel {
|
|||
updated.setConsentRequired(consentRequired);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isServiceAccountsEnabled() {
|
||||
if (updated != null) return updated.isServiceAccountsEnabled();
|
||||
return cached.isServiceAccountsEnabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setServiceAccountsEnabled(boolean serviceAccountsEnabled) {
|
||||
getDelegateForUpdate();
|
||||
updated.setServiceAccountsEnabled(serviceAccountsEnabled);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RoleModel getRole(String name) {
|
||||
if (updated != null) return updated.getRole(name);
|
||||
|
|
|
@ -241,6 +241,11 @@ public class DefaultCacheUserProvider implements CacheUserProvider {
|
|||
return getDelegate().searchForUserByAttributes(attributes, realm, firstResult, maxResults);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<UserModel> searchForUserByUserAttributes(Map<String, String> attributes, RealmModel realm) {
|
||||
return getDelegate().searchForUserByUserAttributes(attributes, realm);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<FederatedIdentityModel> getFederatedIdentities(UserModel user, RealmModel realm) {
|
||||
return getDelegate().getFederatedIdentities(user, realm);
|
||||
|
|
|
@ -108,6 +108,11 @@ public class NoCacheUserProvider implements CacheUserProvider {
|
|||
return getDelegate().searchForUserByAttributes(attributes, realm, firstResult, maxResults);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<UserModel> searchForUserByUserAttributes(Map<String, String> attributes, RealmModel realm) {
|
||||
return getDelegate().searchForUserByUserAttributes(attributes, realm);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<FederatedIdentityModel> getFederatedIdentities(UserModel user, RealmModel realm) {
|
||||
return getDelegate().getFederatedIdentities(user, realm);
|
||||
|
|
|
@ -46,6 +46,7 @@ public class CachedClient implements Serializable {
|
|||
private List<String> defaultRoles = new LinkedList<String>();
|
||||
private boolean bearerOnly;
|
||||
private boolean consentRequired;
|
||||
private boolean serviceAccountsEnabled;
|
||||
private Map<String, String> roles = new HashMap<String, String>();
|
||||
private int nodeReRegistrationTimeout;
|
||||
private Map<String, Integer> registeredNodes;
|
||||
|
@ -78,6 +79,7 @@ public class CachedClient implements Serializable {
|
|||
defaultRoles.addAll(model.getDefaultRoles());
|
||||
bearerOnly = model.isBearerOnly();
|
||||
consentRequired = model.isConsentRequired();
|
||||
serviceAccountsEnabled = model.isServiceAccountsEnabled();
|
||||
for (RoleModel role : model.getRoles()) {
|
||||
roles.put(role.getName(), role.getId());
|
||||
cache.addCachedRole(new CachedClientRole(id, role, realm));
|
||||
|
@ -178,6 +180,10 @@ public class CachedClient implements Serializable {
|
|||
return consentRequired;
|
||||
}
|
||||
|
||||
public boolean isServiceAccountsEnabled() {
|
||||
return serviceAccountsEnabled;
|
||||
}
|
||||
|
||||
public Map<String, String> getRoles() {
|
||||
return roles;
|
||||
}
|
||||
|
|
|
@ -460,6 +460,16 @@ public class ClientAdapter implements ClientModel {
|
|||
entity.setConsentRequired(consentRequired);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isServiceAccountsEnabled() {
|
||||
return entity.isServiceAccountsEnabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setServiceAccountsEnabled(boolean serviceAccountsEnabled) {
|
||||
entity.setServiceAccountsEnabled(serviceAccountsEnabled);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDirectGrantsOnly() {
|
||||
return entity.isDirectGrantsOnly();
|
||||
|
|
|
@ -18,8 +18,10 @@ import org.keycloak.models.utils.CredentialValidation;
|
|||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.Query;
|
||||
import javax.persistence.TypedQuery;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
@ -379,6 +381,38 @@ public class JpaUserProvider implements UserProvider {
|
|||
return users;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<UserModel> searchForUserByUserAttributes(Map<String, String> attributes, RealmModel realm) {
|
||||
StringBuilder builder = new StringBuilder("select attr.user,count(attr.user) from UserAttributeEntity attr where attr.user.realmId = :realmId");
|
||||
boolean first = true;
|
||||
for (Map.Entry<String, String> entry : attributes.entrySet()) {
|
||||
String attrName = entry.getKey();
|
||||
if (first) {
|
||||
builder.append(" and ");
|
||||
first = false;
|
||||
} else {
|
||||
builder.append(" or ");
|
||||
}
|
||||
builder.append(" ( attr.name like :").append(attrName);
|
||||
builder.append(" and attr.value like :").append(attrName).append("val )");
|
||||
}
|
||||
builder.append(" group by attr.user having count(attr.user) = " + attributes.size());
|
||||
Query query = em.createQuery(builder.toString());
|
||||
query.setParameter("realmId", realm.getId());
|
||||
for (Map.Entry<String, String> entry : attributes.entrySet()) {
|
||||
query.setParameter(entry.getKey(), entry.getKey());
|
||||
query.setParameter(entry.getKey() + "val", entry.getValue());
|
||||
}
|
||||
List results = query.getResultList();
|
||||
|
||||
List<UserModel> users = new ArrayList<UserModel>();
|
||||
for (Object o : results) {
|
||||
UserEntity user = (UserEntity) ((Object[])o)[0];
|
||||
users.add(new UserAdapter(realm, em, user));
|
||||
}
|
||||
return users;
|
||||
}
|
||||
|
||||
private FederatedIdentityEntity findFederatedIdentity(UserModel user, String identityProvider) {
|
||||
TypedQuery<FederatedIdentityEntity> query = em.createNamedQuery("findFederatedIdentityByUserAndProvider", FederatedIdentityEntity.class);
|
||||
UserEntity userEntity = em.getReference(UserEntity.class, user.getId());
|
||||
|
|
|
@ -95,6 +95,9 @@ public class ClientEntity {
|
|||
@Column(name="CONSENT_REQUIRED")
|
||||
private boolean consentRequired;
|
||||
|
||||
@Column(name="SERVICE_ACCOUNTS_ENABLED")
|
||||
private boolean serviceAccountsEnabled;
|
||||
|
||||
@Column(name="NODE_REREG_TIMEOUT")
|
||||
private int nodeReRegistrationTimeout;
|
||||
|
||||
|
@ -295,6 +298,14 @@ public class ClientEntity {
|
|||
this.consentRequired = consentRequired;
|
||||
}
|
||||
|
||||
public boolean isServiceAccountsEnabled() {
|
||||
return serviceAccountsEnabled;
|
||||
}
|
||||
|
||||
public void setServiceAccountsEnabled(boolean serviceAccountsEnabled) {
|
||||
this.serviceAccountsEnabled = serviceAccountsEnabled;
|
||||
}
|
||||
|
||||
public boolean isDirectGrantsOnly() {
|
||||
return directGrantsOnly;
|
||||
}
|
||||
|
|
|
@ -461,6 +461,17 @@ public class ClientAdapter extends AbstractMongoAdapter<MongoClientEntity> imple
|
|||
updateMongoEntity();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isServiceAccountsEnabled() {
|
||||
return getMongoEntity().isServiceAccountsEnabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setServiceAccountsEnabled(boolean serviceAccountsEnabled) {
|
||||
getMongoEntity().setServiceAccountsEnabled(serviceAccountsEnabled);
|
||||
updateMongoEntity();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDirectGrantsOnly() {
|
||||
return getMongoEntity().isDirectGrantsOnly();
|
||||
|
|
|
@ -214,6 +214,19 @@ public class MongoUserProvider implements UserProvider {
|
|||
return convertUserEntities(realm, users);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<UserModel> searchForUserByUserAttributes(Map<String, String> attributes, RealmModel realm) {
|
||||
QueryBuilder queryBuilder = new QueryBuilder()
|
||||
.and("realmId").is(realm.getId());
|
||||
|
||||
for (Map.Entry<String, String> entry : attributes.entrySet()) {
|
||||
queryBuilder.and("attributes." + entry.getKey()).is(entry.getValue());
|
||||
}
|
||||
|
||||
List<MongoUserEntity> users = getMongoStore().loadEntities(MongoUserEntity.class, queryBuilder.get(), invocationContext);
|
||||
return convertUserEntities(realm, users);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<FederatedIdentityModel> getFederatedIdentities(UserModel userModel, RealmModel realm) {
|
||||
UserModel user = getUserById(userModel.getId(), realm);
|
||||
|
|
|
@ -0,0 +1,165 @@
|
|||
package org.keycloak.protocol.oidc;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.ws.rs.core.HttpHeaders;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.MultivaluedMap;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.UriInfo;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.jboss.resteasy.spi.HttpRequest;
|
||||
import org.keycloak.ClientConnection;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.constants.ServiceAccountConstants;
|
||||
import org.keycloak.events.Details;
|
||||
import org.keycloak.events.Errors;
|
||||
import org.keycloak.events.EventBuilder;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.ClientSessionModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.ModelDuplicateException;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
import org.keycloak.models.UserSessionProvider;
|
||||
import org.keycloak.protocol.oidc.utils.AuthorizeClientUtil;
|
||||
import org.keycloak.representations.AccessTokenResponse;
|
||||
import org.keycloak.services.ErrorResponseException;
|
||||
import org.keycloak.services.Urls;
|
||||
import org.keycloak.services.managers.AuthenticationManager;
|
||||
import org.keycloak.services.managers.ClientManager;
|
||||
import org.keycloak.services.managers.RealmManager;
|
||||
import org.keycloak.services.resources.Cors;
|
||||
|
||||
/**
|
||||
* Endpoint for authenticate clients and retrieve service accounts
|
||||
*
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class ServiceAccountManager {
|
||||
|
||||
protected static final Logger logger = Logger.getLogger(ServiceAccountManager.class);
|
||||
|
||||
private TokenManager tokenManager;
|
||||
private AuthenticationManager authManager;
|
||||
private EventBuilder event;
|
||||
private HttpRequest request;
|
||||
private MultivaluedMap<String, String> formParams;
|
||||
|
||||
private KeycloakSession session;
|
||||
|
||||
private RealmModel realm;
|
||||
private HttpHeaders headers;
|
||||
private UriInfo uriInfo;
|
||||
private ClientConnection clientConnection;
|
||||
|
||||
private ClientModel client;
|
||||
private UserModel clientUser;
|
||||
|
||||
public ServiceAccountManager(TokenManager tokenManager, AuthenticationManager authManager, EventBuilder event, HttpRequest request, MultivaluedMap<String, String> formParams, KeycloakSession session) {
|
||||
this.tokenManager = tokenManager;
|
||||
this.authManager = authManager;
|
||||
this.event = event;
|
||||
this.request = request;
|
||||
this.formParams = formParams;
|
||||
this.session = session;
|
||||
|
||||
this.realm = session.getContext().getRealm();
|
||||
this.headers = session.getContext().getRequestHeaders();
|
||||
this.uriInfo = session.getContext().getUri();
|
||||
this.clientConnection = session.getContext().getConnection();
|
||||
}
|
||||
|
||||
public Response buildClientCredentialsGrant() {
|
||||
authenticateClient();
|
||||
checkClient();
|
||||
return finishClientAuthorization();
|
||||
}
|
||||
|
||||
protected void authenticateClient() {
|
||||
// TODO: This should be externalized into pluggable SPI for client authentication (hopefully Authentication SPI can be reused).
|
||||
// Right now, just Client Credentials Grants (as per OAuth2 specs) is supported
|
||||
String authorizationHeader = headers.getRequestHeaders().getFirst(HttpHeaders.AUTHORIZATION);
|
||||
client = AuthorizeClientUtil.authorizeClient(authorizationHeader, formParams, event, realm);
|
||||
event.detail(Details.CLIENT_AUTH_METHOD, Details.CLIENT_AUTH_METHOD_VALUE_CLIENT_CREDENTIALS);
|
||||
}
|
||||
|
||||
protected void checkClient() {
|
||||
if (client.isBearerOnly()) {
|
||||
event.error(Errors.INVALID_CLIENT);
|
||||
throw new ErrorResponseException("unauthorized_client", "Bearer-only client not allowed to retrieve service account", Response.Status.UNAUTHORIZED);
|
||||
}
|
||||
if (client.isPublicClient()) {
|
||||
event.error(Errors.INVALID_CLIENT);
|
||||
throw new ErrorResponseException("unauthorized_client", "Public client not allowed to retrieve service account", Response.Status.UNAUTHORIZED);
|
||||
}
|
||||
if (!client.isServiceAccountsEnabled()) {
|
||||
event.error(Errors.INVALID_CLIENT);
|
||||
throw new ErrorResponseException("unauthorized_client", "Client not enabled to retrieve service account", Response.Status.UNAUTHORIZED);
|
||||
}
|
||||
}
|
||||
|
||||
protected Response finishClientAuthorization() {
|
||||
event.detail(Details.RESPONSE_TYPE, ServiceAccountConstants.CLIENT_AUTH);
|
||||
|
||||
Map<String, String> search = new HashMap<>();
|
||||
search.put(ServiceAccountConstants.SERVICE_ACCOUNT_CLIENT_ATTRIBUTE, client.getId());
|
||||
List<UserModel> users = session.users().searchForUserByUserAttributes(search, realm);
|
||||
|
||||
if (users.size() == 0) {
|
||||
// May need to handle bootstrap here as well
|
||||
logger.warnf("Service account user for client '%s' not found. Creating now", client.getClientId());
|
||||
new ClientManager(new RealmManager(session)).enableServiceAccount(client);
|
||||
users = session.users().searchForUserByUserAttributes(search, realm);
|
||||
clientUser = users.get(0);
|
||||
} else if (users.size() == 1) {
|
||||
clientUser = users.get(0);
|
||||
} else {
|
||||
throw new ModelDuplicateException("Multiple service account users found for client '" + client.getClientId() + "' . Check your DB");
|
||||
}
|
||||
|
||||
String clientUsername = clientUser.getUsername();
|
||||
event.detail(Details.USERNAME, clientUsername);
|
||||
event.user(clientUser);
|
||||
|
||||
if (!clientUser.isEnabled()) {
|
||||
event.error(Errors.USER_DISABLED);
|
||||
throw new ErrorResponseException("invalid_request", "User '" + clientUsername + "' disabled", Response.Status.UNAUTHORIZED);
|
||||
}
|
||||
|
||||
String scope = formParams.getFirst(OAuth2Constants.SCOPE);
|
||||
|
||||
UserSessionProvider sessions = session.sessions();
|
||||
|
||||
// TODO: Once more requirements are added, clientSession will be likely created earlier by authentication mechanism
|
||||
ClientSessionModel clientSession = sessions.createClientSession(realm, client);
|
||||
clientSession.setAuthMethod(OIDCLoginProtocol.LOGIN_PROTOCOL);
|
||||
clientSession.setNote(OIDCLoginProtocol.ISSUER, Urls.realmIssuer(uriInfo.getBaseUri(), realm.getName()));
|
||||
|
||||
// TODO: Should rather obtain authMethod from client session?
|
||||
UserSessionModel userSession = sessions.createUserSession(realm, clientUser, clientUsername, clientConnection.getRemoteAddr(), ServiceAccountConstants.CLIENT_AUTH, false, null, null);
|
||||
event.session(userSession);
|
||||
|
||||
TokenManager.attachClientSession(userSession, clientSession);
|
||||
|
||||
// Notes about client details
|
||||
userSession.setNote(ServiceAccountConstants.CLIENT_ID, client.getClientId());
|
||||
userSession.setNote(ServiceAccountConstants.CLIENT_HOST, clientConnection.getRemoteHost());
|
||||
userSession.setNote(ServiceAccountConstants.CLIENT_ADDRESS, clientConnection.getRemoteAddr());
|
||||
|
||||
AccessTokenResponse res = tokenManager.responseBuilder(realm, client, event, session, userSession, clientSession)
|
||||
.generateAccessToken(session, scope, client, clientUser, userSession, clientSession)
|
||||
.generateRefreshToken()
|
||||
.generateIDToken()
|
||||
.build();
|
||||
|
||||
event.success();
|
||||
|
||||
return Cors.add(request, Response.ok(res, MediaType.APPLICATION_JSON_TYPE)).auth().allowedOrigins(client).allowedMethods("POST").exposedHeaders(Cors.ACCESS_CONTROL_ALLOW_METHODS).build();
|
||||
}
|
||||
|
||||
}
|
|
@ -22,6 +22,7 @@ import org.keycloak.models.UserSessionProvider;
|
|||
import org.keycloak.models.utils.DefaultAuthenticationFlows;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||
import org.keycloak.protocol.oidc.ServiceAccountManager;
|
||||
import org.keycloak.protocol.oidc.TokenManager;
|
||||
import org.keycloak.protocol.oidc.utils.AuthorizeClientUtil;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
|
@ -53,7 +54,7 @@ public class TokenEndpoint {
|
|||
private ClientModel client;
|
||||
|
||||
private enum Action {
|
||||
AUTHORIZATION_CODE, REFRESH_TOKEN, PASSWORD
|
||||
AUTHORIZATION_CODE, REFRESH_TOKEN, PASSWORD, CLIENT_CREDENTIALS
|
||||
}
|
||||
|
||||
@Context
|
||||
|
@ -97,7 +98,11 @@ public class TokenEndpoint {
|
|||
checkSsl();
|
||||
checkRealm();
|
||||
checkGrantType();
|
||||
|
||||
// client grant type will do it's own verification of client
|
||||
if (!grantType.equals(OAuth2Constants.CLIENT_CREDENTIALS)) {
|
||||
checkClient();
|
||||
}
|
||||
|
||||
switch (action) {
|
||||
case AUTHORIZATION_CODE:
|
||||
|
@ -106,6 +111,8 @@ public class TokenEndpoint {
|
|||
return buildRefreshToken();
|
||||
case PASSWORD:
|
||||
return buildResourceOwnerPasswordCredentialsGrant();
|
||||
case CLIENT_CREDENTIALS:
|
||||
return buildClientCredentialsGrant();
|
||||
}
|
||||
|
||||
throw new RuntimeException("Unknown action " + action);
|
||||
|
@ -144,7 +151,7 @@ public class TokenEndpoint {
|
|||
String authorizationHeader = headers.getRequestHeaders().getFirst(HttpHeaders.AUTHORIZATION);
|
||||
client = AuthorizeClientUtil.authorizeClient(authorizationHeader, formParams, event, realm);
|
||||
|
||||
if ((client instanceof ClientModel) && ((ClientModel) client).isBearerOnly()) {
|
||||
if (client.isBearerOnly()) {
|
||||
throw new ErrorResponseException("invalid_client", "Bearer-only not allowed", Response.Status.BAD_REQUEST);
|
||||
}
|
||||
}
|
||||
|
@ -167,6 +174,9 @@ public class TokenEndpoint {
|
|||
} else if (grantType.equals(OAuth2Constants.PASSWORD)) {
|
||||
event.event(EventType.LOGIN);
|
||||
action = Action.PASSWORD;
|
||||
} else if (grantType.equals(OAuth2Constants.CLIENT_CREDENTIALS)) {
|
||||
event.event(EventType.CLIENT_LOGIN);
|
||||
action = Action.CLIENT_CREDENTIALS;
|
||||
} else {
|
||||
throw new ErrorResponseException(Errors.INVALID_REQUEST, "Invalid " + OIDCLoginProtocol.GRANT_TYPE_PARAM, Response.Status.BAD_REQUEST);
|
||||
}
|
||||
|
@ -355,4 +365,9 @@ public class TokenEndpoint {
|
|||
return Cors.add(request, Response.ok(res, MediaType.APPLICATION_JSON_TYPE)).auth().allowedOrigins(client).allowedMethods("POST").exposedHeaders(Cors.ACCESS_CONTROL_ALLOW_METHODS).build();
|
||||
}
|
||||
|
||||
public Response buildClientCredentialsGrant() {
|
||||
ServiceAccountManager serviceAccountManager = new ServiceAccountManager(tokenManager, authManager, event, request, formParams, session);
|
||||
return serviceAccountManager.buildClientCredentialsGrant();
|
||||
}
|
||||
|
||||
}
|
|
@ -3,10 +3,15 @@ package org.keycloak.services.managers;
|
|||
import org.codehaus.jackson.annotate.JsonProperty;
|
||||
import org.codehaus.jackson.annotate.JsonPropertyOrder;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.constants.ServiceAccountConstants;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.ProtocolMapperModel;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.UserSessionProvider;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||
import org.keycloak.protocol.oidc.mappers.UserSessionNoteMapper;
|
||||
import org.keycloak.representations.adapters.config.BaseRealmConfig;
|
||||
import org.keycloak.representations.idm.CredentialRepresentation;
|
||||
import org.keycloak.util.Time;
|
||||
|
@ -84,6 +89,57 @@ public class ClientManager {
|
|||
return validatedNodes;
|
||||
}
|
||||
|
||||
public void enableServiceAccount(ClientModel client) {
|
||||
client.setServiceAccountsEnabled(true);
|
||||
|
||||
// Add dedicated user for this service account
|
||||
RealmModel realm = client.getRealm();
|
||||
Map<String, String> search = new HashMap<>();
|
||||
search.put(ServiceAccountConstants.SERVICE_ACCOUNT_CLIENT_ATTRIBUTE, client.getId());
|
||||
List<UserModel> serviceAccountUsers = realmManager.getSession().users().searchForUserByUserAttributes(search, realm);
|
||||
if (serviceAccountUsers.size() == 0) {
|
||||
String username = ServiceAccountConstants.SERVICE_ACCOUNT_USER_PREFIX + client.getClientId();
|
||||
logger.infof("Creating service account user '%s'", username);
|
||||
|
||||
UserModel user = realmManager.getSession().users().addUser(realm, username);
|
||||
user.setEnabled(true);
|
||||
user.setEmail(username + "@placeholder.org");
|
||||
user.setSingleAttribute(ServiceAccountConstants.SERVICE_ACCOUNT_CLIENT_ATTRIBUTE, client.getId());
|
||||
}
|
||||
|
||||
// Add protocol mappers to retrieve clientId in access token
|
||||
if (client.getProtocolMapperByName(OIDCLoginProtocol.LOGIN_PROTOCOL, ServiceAccountConstants.CLIENT_ID_PROTOCOL_MAPPER) == null) {
|
||||
logger.debugf("Creating service account protocol mapper '%s' for client '%s'", ServiceAccountConstants.CLIENT_ID_PROTOCOL_MAPPER, client.getClientId());
|
||||
ProtocolMapperModel protocolMapper = UserSessionNoteMapper.createClaimMapper(ServiceAccountConstants.CLIENT_ID_PROTOCOL_MAPPER,
|
||||
ServiceAccountConstants.CLIENT_ID,
|
||||
ServiceAccountConstants.CLIENT_ID, "String",
|
||||
false, "",
|
||||
true, true);
|
||||
client.addProtocolMapper(protocolMapper);
|
||||
}
|
||||
|
||||
// Add protocol mappers to retrieve hostname and IP address of client in access token
|
||||
if (client.getProtocolMapperByName(OIDCLoginProtocol.LOGIN_PROTOCOL, ServiceAccountConstants.CLIENT_HOST_PROTOCOL_MAPPER) == null) {
|
||||
logger.debugf("Creating service account protocol mapper '%s' for client '%s'", ServiceAccountConstants.CLIENT_HOST_PROTOCOL_MAPPER, client.getClientId());
|
||||
ProtocolMapperModel protocolMapper = UserSessionNoteMapper.createClaimMapper(ServiceAccountConstants.CLIENT_HOST_PROTOCOL_MAPPER,
|
||||
ServiceAccountConstants.CLIENT_HOST,
|
||||
ServiceAccountConstants.CLIENT_HOST, "String",
|
||||
false, "",
|
||||
true, true);
|
||||
client.addProtocolMapper(protocolMapper);
|
||||
}
|
||||
|
||||
if (client.getProtocolMapperByName(OIDCLoginProtocol.LOGIN_PROTOCOL, ServiceAccountConstants.CLIENT_ADDRESS_PROTOCOL_MAPPER) == null) {
|
||||
logger.debugf("Creating service account protocol mapper '%s' for client '%s'", ServiceAccountConstants.CLIENT_ADDRESS_PROTOCOL_MAPPER, client.getClientId());
|
||||
ProtocolMapperModel protocolMapper = UserSessionNoteMapper.createClaimMapper(ServiceAccountConstants.CLIENT_ADDRESS_PROTOCOL_MAPPER,
|
||||
ServiceAccountConstants.CLIENT_ADDRESS,
|
||||
ServiceAccountConstants.CLIENT_ADDRESS, "String",
|
||||
false, "",
|
||||
true, true);
|
||||
client.addProtocolMapper(protocolMapper);
|
||||
}
|
||||
}
|
||||
|
||||
@JsonPropertyOrder({"realm", "realm-public-key", "bearer-only", "auth-server-url", "ssl-required",
|
||||
"resource", "public-client", "credentials",
|
||||
"use-resource-role-mappings"})
|
||||
|
|
|
@ -101,6 +101,10 @@ public class ClientResource {
|
|||
auth.requireManage();
|
||||
|
||||
try {
|
||||
if (rep.isServiceAccountsEnabled() && !client.isServiceAccountsEnabled()) {
|
||||
new ClientManager(new RealmManager(session)).enableServiceAccount(client);;
|
||||
}
|
||||
|
||||
RepresentationToModel.updateClient(rep, client);
|
||||
adminEvent.operation(OperationType.UPDATE).resourcePath(uriInfo).representation(rep).success();
|
||||
return Response.noContent().build();
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue