Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -27,26 +27,23 @@
*/
public class DefaultMongoConnectionFactoryProvider implements MongoConnectionProviderFactory {

// TODO Make configurable
// TODO Make it dynamic
private String[] entities = new String[]{
"org.keycloak.models.mongo.keycloak.entities.MongoRealmEntity",
"org.keycloak.models.mongo.keycloak.entities.MongoUserEntity",
"org.keycloak.models.mongo.keycloak.entities.MongoRoleEntity",
"org.keycloak.models.mongo.keycloak.entities.MongoClientEntity",
"org.keycloak.models.mongo.keycloak.entities.MongoUserConsentEntity",
"org.keycloak.models.mongo.keycloak.entities.MongoMigrationModelEntity",
"org.keycloak.models.entities.IdentityProviderEntity",
"org.keycloak.models.entities.ClientIdentityProviderMappingEntity",
"org.keycloak.models.entities.RequiredCredentialEntity",
"org.keycloak.models.entities.CredentialEntity",
"org.keycloak.models.entities.FederatedIdentityEntity",
"org.keycloak.models.mongo.keycloak.entities.MongoClientEntity",
"org.keycloak.models.sessions.mongo.entities.MongoUsernameLoginFailureEntity",
"org.keycloak.models.sessions.mongo.entities.MongoUserSessionEntity",
"org.keycloak.models.sessions.mongo.entities.MongoClientSessionEntity",
"org.keycloak.models.entities.UserFederationProviderEntity",
"org.keycloak.models.entities.UserFederationMapperEntity",
"org.keycloak.models.entities.ProtocolMapperEntity",
"org.keycloak.models.entities.IdentityProviderMapperEntity",
"org.keycloak.models.mongo.keycloak.entities.MongoUserConsentEntity",
"org.keycloak.models.mongo.keycloak.entities.MongoMigrationModelEntity",
"org.keycloak.models.entities.AuthenticationExecutionEntity",
"org.keycloak.models.entities.AuthenticationFlowEntity",
"org.keycloak.models.entities.AuthenticatorConfigEntity",
Expand Down
6 changes: 3 additions & 3 deletions core/src/main/java/org/keycloak/AbstractOAuthClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public class AbstractOAuthClient {
private final AtomicLong counter = new AtomicLong();

protected String clientId;
protected Map<String, String> credentials;
protected Map<String, Object> credentials;
protected String authUrl;
protected String tokenUrl;
protected RelativeUrlsUsed relativeUrlsUsed;
Expand All @@ -37,11 +37,11 @@ public void setClientId(String clientId) {
this.clientId = clientId;
}

public Map<String, String> getCredentials() {
public Map<String, Object> getCredentials() {
return credentials;
}

public void setCredentials(Map<String, String> credentials) {
public void setCredentials(Map<String, Object> credentials) {
this.credentials = credentials;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public class BaseAdapterConfig extends BaseRealmConfig {
@JsonProperty("public-client")
protected boolean publicClient;
@JsonProperty("credentials")
protected Map<String, String> credentials = new HashMap<String, String>();
protected Map<String, Object> credentials = new HashMap<>();


public boolean isUseResourceRoleMappings() {
Expand Down Expand Up @@ -114,11 +114,11 @@ public void setEnableBasicAuth(boolean enableBasicAuth) {
this.enableBasicAuth = enableBasicAuth;
}

public Map<String, String> getCredentials() {
public Map<String, Object> getCredentials() {
return credentials;
}

public void setCredentials(Map<String, String> credentials) {
public void setCredentials(Map<String, Object> credentials) {
this.credentials = credentials;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package org.keycloak.example;

/**
* Client authentication with traditional OAuth2 client_id + client_secret
*
* @author <a href="mailto:[email protected]">Marek Posolda</a>
*/
public class ProductSAClientSecretServlet extends ProductServiceAccountServlet {

@Override
protected String getAdapterConfigLocation() {
return "WEB-INF/keycloak-client-secret.json";
}

@Override
protected String getClientAuthenticationMethod() {
return "secret";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package org.keycloak.example;

/**
* @author <a href="mailto:[email protected]">Marek Posolda</a>
*/
public class ProductSAClientSignedJWTServlet extends ProductServiceAccountServlet {

@Override
protected String getAdapterConfigLocation() {
return "WEB-INF/keycloak-client-signed-jwt.json";
}

@Override
protected String getClientAuthenticationMethod() {
return "jwt";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
Expand All @@ -26,30 +28,46 @@
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.KeycloakDeploymentBuilder;
import org.keycloak.adapters.ServerRequest;
import org.keycloak.constants.ServiceAccountConstants;
import org.keycloak.adapters.authentication.ClientCredentialsProviderUtils;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.util.BasicAuthHelper;
import org.keycloak.util.JsonSerialization;

/**
* @author <a href="mailto:[email protected]">Marek Posolda</a>
*/
public class ProductServiceAccountServlet extends HttpServlet {
public abstract 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";
public static final String CLIENT_AUTH_METHOD = "clientAuthMethod";

protected abstract String getAdapterConfigLocation();
protected abstract String getClientAuthenticationMethod();

public static String getLoginUrl(HttpServletRequest request) {
return "/service-account-portal/app-" + request.getAttribute(CLIENT_AUTH_METHOD) + "/login";
}

public static String getRefreshUrl(HttpServletRequest request) {
return "/service-account-portal/app-" + request.getAttribute(CLIENT_AUTH_METHOD) + "/refresh";
}

public static String getLogoutUrl(HttpServletRequest request) {
return "/service-account-portal/app-" + request.getAttribute(CLIENT_AUTH_METHOD) + "/logout";
}

@Override
public void init() throws ServletException {
InputStream config = getServletContext().getResourceAsStream("WEB-INF/keycloak.json");
String adapterConfigLocation = getAdapterConfigLocation();
InputStream config = getServletContext().getResourceAsStream(adapterConfigLocation);
KeycloakDeployment deployment = KeycloakDeploymentBuilder.build(config);
HttpClient client = new DefaultHttpClient();
getServletContext().setAttribute("deployment-" + getClientAuthenticationMethod(), deployment);

getServletContext().setAttribute(KeycloakDeployment.class.getName(), deployment);
HttpClient client = new DefaultHttpClient();
getServletContext().setAttribute(HttpClient.class.getName(), client);
}

Expand All @@ -60,6 +78,8 @@ public void destroy() {

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setAttribute(CLIENT_AUTH_METHOD, getClientAuthenticationMethod());

String reqUri = req.getRequestURI();
if (reqUri.endsWith("/login")) {
serviceAccountLogin(req);
Expand All @@ -81,16 +101,21 @@ 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);
// Add client credentials according to the method configured in keycloak.json file
Map<String, String> reqHeaders = new HashMap<>();
Map<String, String> reqParams = new HashMap<>();
ClientCredentialsProviderUtils.setClientCredentials(deployment, reqHeaders, reqParams);
for (Map.Entry<String, String> header : reqHeaders.entrySet()) {
post.setHeader(header.getKey(), header.getValue());
}
for (Map.Entry<String, String> param : reqParams.entrySet()) {
formparams.add(new BasicNameValuePair(param.getKey(), param.getValue()));
}

UrlEncodedFormEntity form = new UrlEncodedFormEntity(formparams, "UTF-8");
post.setEntity(form);
Expand Down Expand Up @@ -217,7 +242,7 @@ private String getContent(HttpEntity entity) throws IOException {
}

private KeycloakDeployment getKeycloakDeployment() {
return (KeycloakDeployment) getServletContext().getAttribute(KeycloakDeployment.class.getName());
return (KeycloakDeployment) getServletContext().getAttribute("deployment-" + getClientAuthenticationMethod());
}

private HttpClient getHttpClient() {
Expand Down
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"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": {
"jwt": {
"client-keystore-file": "classpath:keystore-client.jks",
"client-keystore-type": "JKS",
"client-keystore-password": "storepass",
"client-key-password": "keypass",
"client-key-alias": "clientkey",
"token-expiration": 10
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,16 @@
AccessToken token = (AccessToken) request.getSession().getAttribute(ProductServiceAccountServlet.TOKEN_PARSED);
String products = (String) request.getAttribute(ProductServiceAccountServlet.PRODUCTS);
String appError = (String) request.getAttribute(ProductServiceAccountServlet.ERROR);
String clientAuthMethod = (String) request.getAttribute(ProductServiceAccountServlet.CLIENT_AUTH_METHOD);

String loginUrl = ProductServiceAccountServlet.getLoginUrl(request);
String refreshUrl = ProductServiceAccountServlet.getRefreshUrl(request);
String logoutUrl = ProductServiceAccountServlet.getLogoutUrl(request);
%>
<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>
<h2>Client authentication method: <%= clientAuthMethod %></h2>
<p><a href="<%= loginUrl %>">Login</a> | <a href="<%= refreshUrl %>">Refresh token</a> | <a
href="<%= logoutUrl %>">Logout</a></p>
<hr />

<% if (appError != null) { %>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,23 @@
<module-name>service-account-portal</module-name>

<servlet>
<servlet-name>ServiceAccountExample</servlet-name>
<servlet-class>org.keycloak.example.ProductServiceAccountServlet</servlet-class>
<servlet-name>ProductSAClientSecretServlet</servlet-name>
<servlet-class>org.keycloak.example.ProductSAClientSecretServlet</servlet-class>
</servlet>

<servlet>
<servlet-name>ProductSAClientSignedJWTServlet</servlet-name>
<servlet-class>org.keycloak.example.ProductSAClientSignedJWTServlet</servlet-class>
</servlet>

<servlet-mapping>
<servlet-name>ProductSAClientSecretServlet</servlet-name>
<url-pattern>/app-secret/*</url-pattern>
</servlet-mapping>

<servlet-mapping>
<servlet-name>ServiceAccountExample</servlet-name>
<url-pattern>/app/*</url-pattern>
<servlet-name>ProductSAClientSignedJWTServlet</servlet-name>
<url-pattern>/app-jwt/*</url-pattern>
</servlet-mapping>

</web-app>
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
<html>
<head>
<meta http-equiv="Refresh" content="0; URL=app">
</head>
<head><title>Service account example</title></head>
<body>
<ul>
<li><a href="app-secret">Client authentication with client secret</a><br /><br /></li>
<li><a href="app-jwt">Client authentication with JWT signed by client private key</a></li>
</ul>
</body>
</html>
5 changes: 4 additions & 1 deletion examples/demo-template/testrealm.json
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,10 @@
"clientId": "product-sa-client",
"enabled": true,
"secret": "password",
"serviceAccountsEnabled": true
"serviceAccountsEnabled": true,
"attributes": {
"jwt.credential.certificate": "MIICnTCCAYUCBgFPPLDaTzANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDDAdjbGllbnQxMB4XDTE1MDgxNzE3MjI0N1oXDTI1MDgxNzE3MjQyN1owEjEQMA4GA1UEAwwHY2xpZW50MTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAIUjjgv+V3s96O+Za9002Lp/trtGuHBeaeVL9dFKMKzO2MPqdRmHB4PqNlDdd28Rwf5Xn6iWdFpyUKOnI/yXDLhdcuFpR0sMNK/C9Lt+hSpPFLuzDqgtPgDotlMxiHIWDOZ7g9/gPYNXbNvjv8nSiyqoguoCQiiafW90bPHsiVLdP7ZIUwCcfi1qQm7FhxRJ1NiW5dvUkuCnnWEf0XR+Wzc5eC9EgB0taLFiPsSEIlWMm5xlahYyXkPdNOqZjiRnrTWm5Y4uk8ZcsD/KbPTf/7t7cQXipVaswgjdYi1kK2/zRwOhg1QwWFX/qmvdd+fLxV0R6VqRDhn7Qep2cxwMxLsCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAKE6OA46sf20bz8LZPoiNsqRwBUDkaMGXfnob7s/hJZIIwDEx0IAQ3uKsG7q9wb+aA6s+v7S340zb2k3IxuhFaHaZpAd4CyR5cn1FHylbzoZ7rI/3ASqHDqpljdJaFqPH+m7nZWtyDvtZf+gkZ8OjsndwsSBK1d/jMZPp29qYbl1+XfO7RCp/jDqro/R3saYFaIFiEZPeKn1hUJn6BO48vxH1xspSu9FmlvDOEAOz4AuM58z4zRMP49GcFdCWr1wkonJUHaSptJaQwmBwLFUkCbE5I1ixGMb7mjEud6Y5jhfzJiZMo2U8RfcjNbrN0diZl3jB6LQIwESnhYSghaTjNQ=="
}
}
],
"clientScopeMappings": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ public void contextInitialized(ServletContextEvent sce) {
}
ServletOAuthClientBuilder.build(is, oauthClient);
logger.info("OAuth client configured and started");
oauthClient.start();
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,6 @@ public void contextInitialized(ServletContextEvent sce) {
ServletContext context = sce.getServletContext();

configureClient(context);

client.start();
context.setAttribute(ServletOAuthClient.class.getName(), client);
}

Expand Down
5 changes: 5 additions & 0 deletions federation/ldap/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,11 @@
<artifactId>jboss-logging</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.keycloak.federation.ldap.idm.model;

import java.util.Collection;
import java.util.Deque;
import java.util.LinkedList;
import java.util.regex.Matcher;
Expand All @@ -26,6 +27,10 @@ public static LDAPDn fromString(String dnString) {

@Override
public String toString() {
return toString(entries);
}

private static String toString(Collection<Entry> entries) {
StringBuilder builder = new StringBuilder();

boolean first = true;
Expand Down Expand Up @@ -62,7 +67,9 @@ public String getFirstRdnAttrName() {
* @return string like "dc=something,dc=org" from the DN like "uid=joe,dc=something,dc=org"
*/
public String getParentDn() {
return new LinkedList<>(entries).remove().toString();
LinkedList<Entry> parentDnEntries = new LinkedList<>(entries);
parentDnEntries.remove();
return toString(parentDnEntries);
}

public void addFirst(String rdnName, String rdnValue) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package org.keycloak.federation.ldap.idm.model;

import org.junit.Assert;
import org.junit.Test;

/**
* @author <a href="mailto:[email protected]">Marek Posolda</a>
*/
public class LDAPDnTest {

@Test
public void testDn() throws Exception {
LDAPDn dn = LDAPDn.fromString("dc=keycloak, dc=org");
dn.addFirst("ou", "People");
Assert.assertEquals("ou=People,dc=keycloak,dc=org", dn.toString());

dn.addFirst("uid", "Johny,Depp");
Assert.assertEquals("uid=Johny\\,Depp,ou=People,dc=keycloak,dc=org", dn.toString());

Assert.assertEquals("ou=People,dc=keycloak,dc=org", dn.getParentDn());
}
}
Loading