diff --git a/README.md b/README.md index 14addf7442..570576c59b 100755 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ To stop the server press `Ctrl + C`. Help and Documentation ---------------------- -* [Documentation](http://keycloak.jboss.org/docs) - User Guide, Admin REST API and Javadocs +* [Documentation](http://www.keycloak.org/documentation.html) - User Guide, Admin REST API and Javadocs * [User Mailing List](https://lists.jboss.org/mailman/listinfo/keycloak-user) - Mailing list to ask for help and general questions about Keycloak * [JIRA](https://issues.jboss.org/projects/KEYCLOAK) - Issue tracker for bugs and feature requests @@ -72,4 +72,4 @@ Contributing License ------- -* [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0) \ No newline at end of file +* [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0) diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/AdapterDeploymentContext.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/AdapterDeploymentContext.java index d7985b0714..925a0dba15 100755 --- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/AdapterDeploymentContext.java +++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/AdapterDeploymentContext.java @@ -17,26 +17,21 @@ package org.keycloak.adapters; -import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.client.HttpClient; -import org.apache.http.client.methods.HttpGet; import org.jboss.logging.Logger; import org.keycloak.adapters.authentication.ClientCredentialsProvider; +import org.keycloak.adapters.authorization.PolicyEnforcer; +import org.keycloak.adapters.rotation.PublicKeyLocator; import org.keycloak.adapters.spi.HttpFacade; import org.keycloak.common.enums.RelativeUrlsUsed; import org.keycloak.common.enums.SslRequired; import org.keycloak.enums.TokenStore; import org.keycloak.representations.adapters.config.AdapterConfig; -import org.keycloak.representations.idm.PublishedRealmRepresentation; -import org.keycloak.util.JsonSerialization; import org.keycloak.common.util.KeycloakUriBuilder; -import java.io.ByteArrayOutputStream; import java.io.IOException; -import java.io.InputStream; import java.net.URI; -import java.security.PublicKey; import java.util.Map; /** @@ -57,7 +52,7 @@ public class AdapterDeploymentContext { * during the application deployment's life cycle. * * @param deployment A KeycloakConfigResolver, possibly missing the Auth - * Server URL and/or Realm Public Key + * Server URL */ public AdapterDeploymentContext(KeycloakDeployment deployment) { this.deployment = deployment; @@ -79,7 +74,6 @@ public class AdapterDeploymentContext { /** * For single-tenant deployments, it complements KeycloakDeployment * by resolving a relative Auth Server's URL based on the current request - * and, if needed, will lazily resolve the Realm's Public Key. * * For multi-tenant deployments, defers the resolution of KeycloakDeployment * to the KeycloakConfigResolver . @@ -98,8 +92,8 @@ public class AdapterDeploymentContext { if (deployment.getAuthServerBaseUrl() == null) return deployment; KeycloakDeployment resolvedDeployment = resolveUrls(deployment, facade); - if (resolvedDeployment.getRealmKey() == null) { - resolveRealmKey(resolvedDeployment); + if (resolvedDeployment.getPublicKeyLocator() == null) { + throw new RuntimeException("KeycloakDeployment was never initialized through appropriate SPIs"); } return resolvedDeployment; } @@ -115,45 +109,6 @@ public class AdapterDeploymentContext { } } - public void resolveRealmKey(KeycloakDeployment deployment) { - if (deployment.getClient() == null) { - throw new RuntimeException("KeycloakDeployment was never initialized through appropriate SPIs"); - } - HttpGet get = new HttpGet(deployment.getRealmInfoUrl()); - try { - HttpResponse response = deployment.getClient().execute(get); - int status = response.getStatusLine().getStatusCode(); - if (status != 200) { - close(response); - throw new RuntimeException("Unable to resolve realm public key remotely, status = " + status); - } - HttpEntity entity = response.getEntity(); - if (entity == null) { - throw new RuntimeException("Unable to resolve realm public key remotely. There was no entity."); - } - InputStream is = entity.getContent(); - try { - ByteArrayOutputStream os = new ByteArrayOutputStream(); - int c; - while ((c = is.read()) != -1) { - os.write(c); - } - byte[] bytes = os.toByteArray(); - String json = new String(bytes); - PublishedRealmRepresentation rep = JsonSerialization.readValue(json, PublishedRealmRepresentation.class); - deployment.setRealmKey(rep.getPublicKey()); - } finally { - try { - is.close(); - } catch (IOException ignored) { - - } - } - } catch (IOException e) { - throw new RuntimeException("Unable to resolve realm public key remotely", e); - } - } - /** * This delegate is used to store temporary, per-request metadata like request resolved URLs. * Ever method is delegated except URL get methods and isConfigured() @@ -207,6 +162,11 @@ public class AdapterDeploymentContext { return (this.unregisterNodeUrl != null) ? this.unregisterNodeUrl : delegate.getUnregisterNodeUrl(); } + @Override + public String getJwksUrl() { + return (this.jwksUrl != null) ? this.jwksUrl : delegate.getJwksUrl(); + } + @Override public String getResourceName() { return delegate.getResourceName(); @@ -223,13 +183,13 @@ public class AdapterDeploymentContext { } @Override - public PublicKey getRealmKey() { - return delegate.getRealmKey(); + public void setPublicKeyLocator(PublicKeyLocator publicKeyLocator) { + delegate.setPublicKeyLocator(publicKeyLocator); } @Override - public void setRealmKey(PublicKey realmKey) { - delegate.setRealmKey(realmKey); + public PublicKeyLocator getPublicKeyLocator() { + return delegate.getPublicKeyLocator(); } @Override @@ -466,6 +426,26 @@ public class AdapterDeploymentContext { public void setTokenMinimumTimeToLive(final int tokenMinimumTimeToLive) { delegate.setTokenMinimumTimeToLive(tokenMinimumTimeToLive); } + + @Override + public PolicyEnforcer getPolicyEnforcer() { + return delegate.getPolicyEnforcer(); + } + + @Override + public void setPolicyEnforcer(PolicyEnforcer policyEnforcer) { + delegate.setPolicyEnforcer(policyEnforcer); + } + + @Override + public void setMinTimeBetweenJwksRequests(int minTimeBetweenJwksRequests) { + delegate.setMinTimeBetweenJwksRequests(minTimeBetweenJwksRequests); + } + + @Override + public int getMinTimeBetweenJwksRequests() { + return delegate.getMinTimeBetweenJwksRequests(); + } } protected KeycloakUriBuilder getBaseBuilder(HttpFacade facade, String base) { diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/BearerTokenRequestAuthenticator.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/BearerTokenRequestAuthenticator.java index 9af6214ba2..70b90b1044 100755 --- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/BearerTokenRequestAuthenticator.java +++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/BearerTokenRequestAuthenticator.java @@ -19,6 +19,7 @@ package org.keycloak.adapters; import org.jboss.logging.Logger; import org.keycloak.RSATokenVerifier; +import org.keycloak.adapters.rotation.AdapterRSATokenVerifier; import org.keycloak.adapters.spi.AuthChallenge; import org.keycloak.adapters.spi.AuthOutcome; import org.keycloak.adapters.spi.HttpFacade; @@ -84,7 +85,7 @@ public class BearerTokenRequestAuthenticator { protected AuthOutcome authenticateToken(HttpFacade exchange, String tokenString) { try { - token = RSATokenVerifier.verifyToken(tokenString, deployment.getRealmKey(), deployment.getRealmInfoUrl()); + token = AdapterRSATokenVerifier.verifyToken(tokenString, deployment); } catch (VerificationException e) { log.error("Failed to verify token", e); challenge = challengeResponse(exchange, OIDCAuthenticationError.Reason.INVALID_TOKEN, "invalid_token", e.getMessage()); diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/CookieTokenStore.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/CookieTokenStore.java index 7f66dbf2e6..2093645533 100755 --- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/CookieTokenStore.java +++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/CookieTokenStore.java @@ -22,6 +22,7 @@ import java.io.IOException; import org.jboss.logging.Logger; import org.keycloak.KeycloakPrincipal; import org.keycloak.RSATokenVerifier; +import org.keycloak.adapters.rotation.AdapterRSATokenVerifier; import org.keycloak.adapters.spi.HttpFacade; import org.keycloak.common.VerificationException; import org.keycloak.constants.AdapterConstants; @@ -73,7 +74,7 @@ public class CookieTokenStore { try { // Skip check if token is active now. It's supposed to be done later by the caller - AccessToken accessToken = RSATokenVerifier.verifyToken(accessTokenString, deployment.getRealmKey(), deployment.getRealmInfoUrl(), false, true); + AccessToken accessToken = AdapterRSATokenVerifier.verifyToken(accessTokenString, deployment, false, true); IDToken idToken; if (idTokenString != null && idTokenString.length() > 0) { try { diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/HttpAdapterUtils.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/HttpAdapterUtils.java new file mode 100644 index 0000000000..e01f7dc32e --- /dev/null +++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/HttpAdapterUtils.java @@ -0,0 +1,79 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.keycloak.adapters; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; + +import org.apache.http.HttpEntity; +import org.apache.http.HttpResponse; +import org.apache.http.client.methods.HttpRequestBase; +import org.keycloak.util.JsonSerialization; + +/** + * @author Marek Posolda + */ +public class HttpAdapterUtils { + + + public static T sendJsonHttpRequest(KeycloakDeployment deployment, HttpRequestBase httpRequest, Class clazz) throws HttpClientAdapterException { + try { + HttpResponse response = deployment.getClient().execute(httpRequest); + int status = response.getStatusLine().getStatusCode(); + if (status != 200) { + close(response); + throw new HttpClientAdapterException("Unexpected status = " + status); + } + HttpEntity entity = response.getEntity(); + if (entity == null) { + throw new HttpClientAdapterException("There was no entity."); + } + InputStream is = entity.getContent(); + try { + ByteArrayOutputStream os = new ByteArrayOutputStream(); + int c; + while ((c = is.read()) != -1) { + os.write(c); + } + byte[] bytes = os.toByteArray(); + String json = new String(bytes); + return JsonSerialization.readValue(json, clazz); + } finally { + try { + is.close(); + } catch (IOException ignored) { + + } + } + } catch (IOException e) { + throw new HttpClientAdapterException("IO error", e); + } + } + + + private static void close(HttpResponse response) { + if (response.getEntity() != null) { + try { + response.getEntity().getContent().close(); + } catch (IOException e) { + + } + } + } +} diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/HttpClientAdapterException.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/HttpClientAdapterException.java new file mode 100644 index 0000000000..7d303e290d --- /dev/null +++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/HttpClientAdapterException.java @@ -0,0 +1,32 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.keycloak.adapters; + +/** + * @author Marek Posolda + */ +public class HttpClientAdapterException extends Exception { + + public HttpClientAdapterException(String message) { + super(message); + } + + public HttpClientAdapterException(String message, Throwable t) { + super(message, t); + } +} diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeployment.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeployment.java index 901b3ea99c..30c40c37ca 100755 --- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeployment.java +++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeployment.java @@ -21,6 +21,7 @@ import org.apache.http.client.HttpClient; import org.jboss.logging.Logger; import org.keycloak.adapters.authentication.ClientCredentialsProvider; import org.keycloak.adapters.authorization.PolicyEnforcer; +import org.keycloak.adapters.rotation.PublicKeyLocator; import org.keycloak.constants.ServiceUrlConstants; import org.keycloak.common.enums.RelativeUrlsUsed; import org.keycloak.common.enums.SslRequired; @@ -29,7 +30,6 @@ import org.keycloak.representations.adapters.config.AdapterConfig; import org.keycloak.common.util.KeycloakUriBuilder; import java.net.URI; -import java.security.PublicKey; import java.util.HashMap; import java.util.Map; @@ -43,7 +43,7 @@ public class KeycloakDeployment { protected RelativeUrlsUsed relativeUrls; protected String realm; - protected volatile PublicKey realmKey; + protected PublicKeyLocator publicKeyLocator; protected String authServerBaseUrl; protected String realmInfoUrl; protected KeycloakUriBuilder authUrl; @@ -52,6 +52,7 @@ public class KeycloakDeployment { protected String accountUrl; protected String registerNodeUrl; protected String unregisterNodeUrl; + protected String jwksUrl; protected String principalAttribute = "sub"; protected String resourceName; @@ -79,13 +80,14 @@ public class KeycloakDeployment { protected volatile int notBefore; protected int tokenMinimumTimeToLive; + protected int minTimeBetweenJwksRequests; private PolicyEnforcer policyEnforcer; public KeycloakDeployment() { } public boolean isConfigured() { - return getRealm() != null && getRealmKey() != null && (isBearerOnly() || getAuthServerBaseUrl() != null); + return getRealm() != null && getPublicKeyLocator() != null && (isBearerOnly() || getAuthServerBaseUrl() != null); } public String getResourceName() { @@ -100,12 +102,12 @@ public class KeycloakDeployment { this.realm = realm; } - public PublicKey getRealmKey() { - return realmKey; + public PublicKeyLocator getPublicKeyLocator() { + return publicKeyLocator; } - public void setRealmKey(PublicKey realmKey) { - this.realmKey = realmKey; + public void setPublicKeyLocator(PublicKeyLocator publicKeyLocator) { + this.publicKeyLocator = publicKeyLocator; } public String getAuthServerBaseUrl() { @@ -147,6 +149,7 @@ public class KeycloakDeployment { accountUrl = authUrlBuilder.clone().path(ServiceUrlConstants.ACCOUNT_SERVICE_PATH).build(getRealm()).toString(); registerNodeUrl = authUrlBuilder.clone().path(ServiceUrlConstants.CLIENTS_MANAGEMENT_REGISTER_NODE_PATH).build(getRealm()).toString(); unregisterNodeUrl = authUrlBuilder.clone().path(ServiceUrlConstants.CLIENTS_MANAGEMENT_UNREGISTER_NODE_PATH).build(getRealm()).toString(); + jwksUrl = authUrlBuilder.clone().path(ServiceUrlConstants.JWKS_URL).build(getRealm()).toString(); } public RelativeUrlsUsed getRelativeUrls() { @@ -181,6 +184,10 @@ public class KeycloakDeployment { return unregisterNodeUrl; } + public String getJwksUrl() { + return jwksUrl; + } + public void setResourceName(String resourceName) { this.resourceName = resourceName; } @@ -369,6 +376,14 @@ public class KeycloakDeployment { this.tokenMinimumTimeToLive = tokenMinimumTimeToLive; } + public int getMinTimeBetweenJwksRequests() { + return minTimeBetweenJwksRequests; + } + + public void setMinTimeBetweenJwksRequests(int minTimeBetweenJwksRequests) { + this.minTimeBetweenJwksRequests = minTimeBetweenJwksRequests; + } + public void setPolicyEnforcer(PolicyEnforcer policyEnforcer) { this.policyEnforcer = policyEnforcer; } diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeploymentBuilder.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeploymentBuilder.java index 5d54df9bab..04e16f895a 100755 --- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeploymentBuilder.java +++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeploymentBuilder.java @@ -22,6 +22,8 @@ import com.fasterxml.jackson.databind.ObjectMapper; import org.jboss.logging.Logger; import org.keycloak.adapters.authentication.ClientCredentialsProviderUtils; import org.keycloak.adapters.authorization.PolicyEnforcer; +import org.keycloak.adapters.rotation.HardcodedPublicKeyLocator; +import org.keycloak.adapters.rotation.JWKPublicKeyLocator; import org.keycloak.common.enums.SslRequired; import org.keycloak.common.util.PemUtils; import org.keycloak.enums.TokenStore; @@ -59,11 +61,16 @@ public class KeycloakDeploymentBuilder { PublicKey realmKey; try { realmKey = PemUtils.decodePublicKey(realmKeyPem); + HardcodedPublicKeyLocator pkLocator = new HardcodedPublicKeyLocator(realmKey); + deployment.setPublicKeyLocator(pkLocator); } catch (Exception e) { throw new RuntimeException(e); } - deployment.setRealmKey(realmKey); + } else { + JWKPublicKeyLocator pkLocator = new JWKPublicKeyLocator(); + deployment.setPublicKeyLocator(pkLocator); } + if (adapterConfig.getSslRequired() != null) { deployment.setSslRequired(SslRequired.valueOf(adapterConfig.getSslRequired().toUpperCase())); } else { @@ -97,6 +104,7 @@ public class KeycloakDeploymentBuilder { deployment.setRegisterNodeAtStartup(adapterConfig.isRegisterNodeAtStartup()); deployment.setRegisterNodePeriod(adapterConfig.getRegisterNodePeriod()); deployment.setTokenMinimumTimeToLive(adapterConfig.getTokenMinimumTimeToLive()); + deployment.setMinTimeBetweenJwksRequests(adapterConfig.getMinTimeBetweenJwksRequests()); if (realmKeyPem == null && adapterConfig.isBearerOnly() && adapterConfig.getAuthServerUrl() == null) { throw new IllegalArgumentException("For bearer auth, you must set the realm-public-key or auth-server-url"); diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/OAuthRequestAuthenticator.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/OAuthRequestAuthenticator.java index 73aa0f52e7..02637c0810 100755 --- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/OAuthRequestAuthenticator.java +++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/OAuthRequestAuthenticator.java @@ -20,6 +20,7 @@ package org.keycloak.adapters; import org.jboss.logging.Logger; import org.keycloak.OAuth2Constants; import org.keycloak.RSATokenVerifier; +import org.keycloak.adapters.rotation.AdapterRSATokenVerifier; import org.keycloak.adapters.spi.AdapterSessionStore; import org.keycloak.adapters.spi.AuthChallenge; import org.keycloak.adapters.spi.AuthOutcome; @@ -342,7 +343,7 @@ public class OAuthRequestAuthenticator { refreshToken = tokenResponse.getRefreshToken(); idTokenString = tokenResponse.getIdToken(); try { - token = RSATokenVerifier.verifyToken(tokenString, deployment.getRealmKey(), deployment.getRealmInfoUrl()); + token = AdapterRSATokenVerifier.verifyToken(tokenString, deployment); if (idTokenString != null) { try { JWSInput input = new JWSInput(idTokenString); diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/PreAuthActionsHandler.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/PreAuthActionsHandler.java index 1a8c735afd..5a1df8c1b6 100755 --- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/PreAuthActionsHandler.java +++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/PreAuthActionsHandler.java @@ -17,7 +17,10 @@ package org.keycloak.adapters; +import java.security.PublicKey; + import org.jboss.logging.Logger; +import org.keycloak.adapters.rotation.AdapterRSATokenVerifier; import org.keycloak.adapters.spi.HttpFacade; import org.keycloak.adapters.spi.UserSessionManagement; import org.keycloak.jose.jws.JWSInputException; @@ -198,7 +201,8 @@ public class PreAuthActionsHandler { try { JWSInput input = new JWSInput(token); - if (RSAProvider.verify(input, deployment.getRealmKey())) { + PublicKey publicKey = AdapterRSATokenVerifier.getPublicKey(input, deployment); + if (RSAProvider.verify(input, publicKey)) { return input; } } catch (JWSInputException ignore) { diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/RefreshableKeycloakSecurityContext.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/RefreshableKeycloakSecurityContext.java index 7cfc7a698b..75f0cb8f2a 100755 --- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/RefreshableKeycloakSecurityContext.java +++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/RefreshableKeycloakSecurityContext.java @@ -21,6 +21,7 @@ import org.jboss.logging.Logger; import org.keycloak.AuthorizationContext; import org.keycloak.KeycloakSecurityContext; import org.keycloak.RSATokenVerifier; +import org.keycloak.adapters.rotation.AdapterRSATokenVerifier; import org.keycloak.common.VerificationException; import org.keycloak.common.util.Time; import org.keycloak.representations.AccessToken; @@ -130,7 +131,7 @@ public class RefreshableKeycloakSecurityContext extends KeycloakSecurityContext String tokenString = response.getToken(); AccessToken token = null; try { - token = RSATokenVerifier.verifyToken(tokenString, deployment.getRealmKey(), deployment.getRealmInfoUrl()); + token = AdapterRSATokenVerifier.verifyToken(tokenString, deployment); log.debug("Token Verification succeeded!"); } catch (VerificationException e) { log.error("failed verification of token"); diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/KeycloakAdapterPolicyEnforcer.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/KeycloakAdapterPolicyEnforcer.java index f1be944353..1c900b8657 100644 --- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/KeycloakAdapterPolicyEnforcer.java +++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/KeycloakAdapterPolicyEnforcer.java @@ -21,6 +21,7 @@ import org.jboss.logging.Logger; import org.keycloak.RSATokenVerifier; import org.keycloak.adapters.KeycloakDeployment; import org.keycloak.adapters.OIDCHttpFacade; +import org.keycloak.adapters.rotation.AdapterRSATokenVerifier; import org.keycloak.adapters.spi.HttpFacade; import org.keycloak.authorization.client.AuthorizationDeniedException; import org.keycloak.authorization.client.AuthzClient; @@ -120,7 +121,7 @@ public class KeycloakAdapterPolicyEnforcer extends AbstractPolicyEnforcer { AuthorizationResponse authzResponse = authzClient.authorization(accessToken).authorize(authzRequest); if (authzResponse != null) { - return RSATokenVerifier.verifyToken(authzResponse.getRpt(), deployment.getRealmKey(), deployment.getRealmInfoUrl()); + return AdapterRSATokenVerifier.verifyToken(authzResponse.getRpt(), deployment); } return null; @@ -130,7 +131,7 @@ public class KeycloakAdapterPolicyEnforcer extends AbstractPolicyEnforcer { if (token.getAuthorization() == null) { EntitlementResponse authzResponse = authzClient.entitlement(accessToken).getAll(authzClient.getConfiguration().getClientId()); - return RSATokenVerifier.verifyToken(authzResponse.getRpt(), deployment.getRealmKey(), deployment.getRealmInfoUrl()); + return AdapterRSATokenVerifier.verifyToken(authzResponse.getRpt(), deployment); } else { EntitlementRequest request = new EntitlementRequest(); PermissionRequest permissionRequest = new PermissionRequest(); @@ -139,7 +140,7 @@ public class KeycloakAdapterPolicyEnforcer extends AbstractPolicyEnforcer { permissionRequest.setScopes(new HashSet<>(pathConfig.getScopes())); request.addPermission(permissionRequest); EntitlementResponse authzResponse = authzClient.entitlement(accessToken).get(authzClient.getConfiguration().getClientId(), request); - return RSATokenVerifier.verifyToken(authzResponse.getRpt(), deployment.getRealmKey(), deployment.getRealmInfoUrl()); + return AdapterRSATokenVerifier.verifyToken(authzResponse.getRpt(), deployment); } } } catch (AuthorizationDeniedException e) { diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/jaas/AbstractKeycloakLoginModule.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/jaas/AbstractKeycloakLoginModule.java index 51be55137f..75086e7804 100755 --- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/jaas/AbstractKeycloakLoginModule.java +++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/jaas/AbstractKeycloakLoginModule.java @@ -43,6 +43,7 @@ import org.keycloak.adapters.AdapterUtils; import org.keycloak.adapters.KeycloakDeployment; import org.keycloak.adapters.KeycloakDeploymentBuilder; import org.keycloak.adapters.RefreshableKeycloakSecurityContext; +import org.keycloak.adapters.rotation.AdapterRSATokenVerifier; import org.keycloak.common.VerificationException; import org.keycloak.common.util.FindFile; import org.keycloak.representations.AccessToken; @@ -88,9 +89,6 @@ public abstract class AbstractKeycloakLoginModule implements LoginModule { try { InputStream is = FindFile.findFile(keycloakConfigFile); KeycloakDeployment kd = KeycloakDeploymentBuilder.build(is); - if (kd.getRealmKey() == null) { - new AdapterDeploymentContext().resolveRealmKey(kd); - } return kd; } catch (RuntimeException e) { getLogger().debug("Unable to find or parse file " + keycloakConfigFile + " due to " + e.getMessage(), e); @@ -190,7 +188,7 @@ public abstract class AbstractKeycloakLoginModule implements LoginModule { protected Auth bearerAuth(String tokenString) throws VerificationException { - AccessToken token = RSATokenVerifier.verifyToken(tokenString, deployment.getRealmKey(), deployment.getRealmInfoUrl()); + AccessToken token = AdapterRSATokenVerifier.verifyToken(tokenString, deployment); boolean verifyCaller; if (deployment.isUseResourceRoleMappings()) { diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/rotation/AdapterRSATokenVerifier.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/rotation/AdapterRSATokenVerifier.java new file mode 100644 index 0000000000..c69ee38d65 --- /dev/null +++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/rotation/AdapterRSATokenVerifier.java @@ -0,0 +1,64 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.keycloak.adapters.rotation; + +import java.security.PublicKey; + +import org.jboss.logging.Logger; +import org.keycloak.RSATokenVerifier; +import org.keycloak.adapters.KeycloakDeployment; +import org.keycloak.common.VerificationException; +import org.keycloak.jose.jws.JWSInput; +import org.keycloak.representations.AccessToken; + +/** + * @author Marek Posolda + */ +public class AdapterRSATokenVerifier { + + private static final Logger log = Logger.getLogger(AdapterRSATokenVerifier.class); + + public static AccessToken verifyToken(String tokenString, KeycloakDeployment deployment) throws VerificationException { + return verifyToken(tokenString, deployment, true, true); + } + + + public static PublicKey getPublicKey(JWSInput input, KeycloakDeployment deployment) throws VerificationException { + PublicKeyLocator pkLocator = deployment.getPublicKeyLocator(); + + PublicKey publicKey = pkLocator.getPublicKey(input, deployment); + if (publicKey == null) { + log.errorf("Didn't find publicKey for kid: %s", input.getHeader().getKeyId()); + throw new VerificationException("Didn't find publicKey for specified kid"); + } + + return publicKey; + } + + public static AccessToken verifyToken(String tokenString, KeycloakDeployment deployment, boolean checkActive, boolean checkTokenType) throws VerificationException { + JWSInput input; + try { + input = new JWSInput(tokenString); + } catch (Exception e) { + throw new VerificationException("Couldn't parse token", e); + } + + PublicKey publicKey = getPublicKey(input, deployment); + return RSATokenVerifier.verifyToken(input, publicKey, deployment.getRealmInfoUrl(), checkActive, checkTokenType); + } +} diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/rotation/HardcodedPublicKeyLocator.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/rotation/HardcodedPublicKeyLocator.java new file mode 100644 index 0000000000..40fb71ad77 --- /dev/null +++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/rotation/HardcodedPublicKeyLocator.java @@ -0,0 +1,40 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.keycloak.adapters.rotation; + +import java.security.PublicKey; + +import org.keycloak.adapters.KeycloakDeployment; +import org.keycloak.jose.jws.JWSInput; + +/** + * @author Marek Posolda + */ +public class HardcodedPublicKeyLocator implements PublicKeyLocator { + + private PublicKey publicKey; + + public HardcodedPublicKeyLocator(PublicKey publicKey) { + this.publicKey = publicKey; + } + + @Override + public PublicKey getPublicKey(JWSInput input, KeycloakDeployment deployment) { + return publicKey; + } +} diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/rotation/JWKPublicKeyLocator.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/rotation/JWKPublicKeyLocator.java new file mode 100644 index 0000000000..500392338c --- /dev/null +++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/rotation/JWKPublicKeyLocator.java @@ -0,0 +1,107 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.keycloak.adapters.rotation; + +import java.security.PublicKey; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.apache.http.client.methods.HttpGet; +import org.jboss.logging.Logger; +import org.keycloak.adapters.HttpAdapterUtils; +import org.keycloak.adapters.HttpClientAdapterException; +import org.keycloak.adapters.KeycloakDeployment; +import org.keycloak.common.util.Time; +import org.keycloak.jose.jwk.JSONWebKeySet; +import org.keycloak.jose.jwk.JWK; +import org.keycloak.jose.jws.JWSInput; +import org.keycloak.util.JWKSUtils; + +/** + * When needed, publicKeys are downloaded by sending request to realm's jwks_url + * + * @author Marek Posolda + */ +public class JWKPublicKeyLocator implements PublicKeyLocator { + + private static final Logger log = Logger.getLogger(JWKPublicKeyLocator.class); + + private Map currentKeys = new ConcurrentHashMap<>(); + + private volatile int lastRequestTime = 0; + + @Override + public PublicKey getPublicKey(JWSInput input, KeycloakDeployment deployment) { + String kid = input.getHeader().getKeyId(); + return getPublicKey(kid, deployment); + } + + + private PublicKey getPublicKey(String kid, KeycloakDeployment deployment) { + int minTimeBetweenRequests = deployment.getMinTimeBetweenJwksRequests(); + + // Check if key is in cache. + PublicKey publicKey = currentKeys.get(kid); + if (publicKey != null) { + return publicKey; + } + + int currentTime = Time.currentTime(); + + // Check if we are allowed to send request + if (currentTime > lastRequestTime + minTimeBetweenRequests) { + synchronized (this) { + currentTime = Time.currentTime(); + if (currentTime > lastRequestTime + minTimeBetweenRequests) { + sendRequest(deployment); + lastRequestTime = currentTime; + } else { + // TODO: debug + log.infof("Won't send request to realm jwks url. Last request time was %d", lastRequestTime); + } + } + } + + return currentKeys.get(kid); + + } + + + private void sendRequest(KeycloakDeployment deployment) { + // Send the request + // TODO: trace or remove? + log.infof("Going to send request to retrieve new set of realm public keys for client %s", deployment.getResourceName()); + + HttpGet getMethod = new HttpGet(deployment.getJwksUrl()); + try { + JSONWebKeySet jwks = HttpAdapterUtils.sendJsonHttpRequest(deployment, getMethod, JSONWebKeySet.class); + + Map publicKeys = JWKSUtils.getKeysForUse(jwks, JWK.Use.SIG); + + // TODO: Debug with condition + log.infof("Realm public keys successfully retrieved for client %s. New kids: %s", deployment.getResourceName(), publicKeys.keySet().toString()); + + // Update current keys + currentKeys.clear(); + currentKeys.putAll(publicKeys); + + } catch (HttpClientAdapterException e) { + log.error("Error when sending request to retrieve realm keys", e); + } + } +} diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/rotation/PublicKeyLocator.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/rotation/PublicKeyLocator.java new file mode 100644 index 0000000000..bda80dc3a3 --- /dev/null +++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/rotation/PublicKeyLocator.java @@ -0,0 +1,37 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.keycloak.adapters.rotation; + +import java.security.PublicKey; + +import org.keycloak.adapters.KeycloakDeployment; +import org.keycloak.jose.jws.JWSInput; + +/** + * @author Marek Posolda + */ +public interface PublicKeyLocator { + + /** + * @param input + * @param deployment + * @return publicKey, which should be used for verify signature on given "input" + */ + PublicKey getPublicKey(JWSInput input, KeycloakDeployment deployment); + +} diff --git a/adapters/oidc/adapter-core/src/test/java/org/keycloak/adapters/KeycloakDeploymentBuilderTest.java b/adapters/oidc/adapter-core/src/test/java/org/keycloak/adapters/KeycloakDeploymentBuilderTest.java index 4be511023c..d1ce748f63 100644 --- a/adapters/oidc/adapter-core/src/test/java/org/keycloak/adapters/KeycloakDeploymentBuilderTest.java +++ b/adapters/oidc/adapter-core/src/test/java/org/keycloak/adapters/KeycloakDeploymentBuilderTest.java @@ -21,6 +21,8 @@ import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager; import org.junit.Test; import org.keycloak.adapters.authentication.ClientIdAndSecretCredentialsProvider; import org.keycloak.adapters.authentication.JWTClientCredentialsProvider; +import org.keycloak.adapters.rotation.HardcodedPublicKeyLocator; +import org.keycloak.adapters.rotation.JWKPublicKeyLocator; import org.keycloak.common.enums.RelativeUrlsUsed; import org.keycloak.common.enums.SslRequired; import org.keycloak.enums.TokenStore; @@ -39,7 +41,11 @@ public class KeycloakDeploymentBuilderTest { KeycloakDeployment deployment = KeycloakDeploymentBuilder.build(getClass().getResourceAsStream("/keycloak.json")); assertEquals("demo", deployment.getRealm()); assertEquals("customer-portal", deployment.getResourceName()); - assertEquals(PemUtils.decodePublicKey("MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB"), deployment.getRealmKey()); + + assertTrue(deployment.getPublicKeyLocator() instanceof HardcodedPublicKeyLocator); + assertEquals(PemUtils.decodePublicKey("MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB"), + deployment.getPublicKeyLocator().getPublicKey(null, deployment)); + assertEquals("https://localhost:8443/auth/realms/demo/protocol/openid-connect/auth", deployment.getAuthUrl().build().toString()); assertEquals(SslRequired.EXTERNAL, deployment.getSslRequired()); assertTrue(deployment.isUseResourceRoleMappings()); @@ -62,12 +68,16 @@ public class KeycloakDeploymentBuilderTest { assertEquals(TokenStore.COOKIE, deployment.getTokenStore()); assertEquals("email", deployment.getPrincipalAttribute()); assertEquals(10, deployment.getTokenMinimumTimeToLive()); + assertEquals(20, deployment.getMinTimeBetweenJwksRequests()); } @Test public void loadNoClientCredentials() throws Exception { KeycloakDeployment deployment = KeycloakDeploymentBuilder.build(getClass().getResourceAsStream("/keycloak-no-credentials.json")); assertEquals(ClientIdAndSecretCredentialsProvider.PROVIDER_ID, deployment.getClientAuthenticator().getId()); + + assertTrue(deployment.getPublicKeyLocator() instanceof JWKPublicKeyLocator); + assertEquals(10, deployment.getMinTimeBetweenJwksRequests()); } @Test diff --git a/adapters/oidc/adapter-core/src/test/resources/keycloak-no-credentials.json b/adapters/oidc/adapter-core/src/test/resources/keycloak-no-credentials.json index 5f223ac75f..a3c4026c34 100644 --- a/adapters/oidc/adapter-core/src/test/resources/keycloak-no-credentials.json +++ b/adapters/oidc/adapter-core/src/test/resources/keycloak-no-credentials.json @@ -1,7 +1,6 @@ { "realm": "demo", "resource": "customer-portal", - "realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB", "auth-server-url": "https://localhost:8443/auth", "public-client": true, "expose-token": true diff --git a/adapters/oidc/adapter-core/src/test/resources/keycloak.json b/adapters/oidc/adapter-core/src/test/resources/keycloak.json index 7bf269f2c1..a8afd22cf2 100644 --- a/adapters/oidc/adapter-core/src/test/resources/keycloak.json +++ b/adapters/oidc/adapter-core/src/test/resources/keycloak.json @@ -29,5 +29,6 @@ "register-node-period": 1000, "token-store": "cookie", "principal-attribute": "email", - "token-minimum-time-to-live": 10 + "token-minimum-time-to-live": 10, + "min-time-between-jwks-requests": 20 } \ No newline at end of file diff --git a/adapters/oidc/installed/src/main/java/org/keycloak/adapters/installed/KeycloakInstalled.java b/adapters/oidc/installed/src/main/java/org/keycloak/adapters/installed/KeycloakInstalled.java index fc0e47fa50..c52a44af65 100644 --- a/adapters/oidc/installed/src/main/java/org/keycloak/adapters/installed/KeycloakInstalled.java +++ b/adapters/oidc/installed/src/main/java/org/keycloak/adapters/installed/KeycloakInstalled.java @@ -20,6 +20,7 @@ package org.keycloak.adapters.installed; import org.keycloak.OAuth2Constants; import org.keycloak.OAuthErrorException; import org.keycloak.RSATokenVerifier; +import org.keycloak.adapters.rotation.AdapterRSATokenVerifier; import org.keycloak.common.VerificationException; import org.keycloak.adapters.KeycloakDeployment; import org.keycloak.adapters.KeycloakDeploymentBuilder; @@ -213,7 +214,7 @@ public class KeycloakInstalled { refreshToken = tokenResponse.getRefreshToken(); idTokenString = tokenResponse.getIdToken(); - token = RSATokenVerifier.verifyToken(tokenString, deployment.getRealmKey(), deployment.getRealmInfoUrl()); + token = AdapterRSATokenVerifier.verifyToken(tokenString, deployment); if (idTokenString != null) { try { JWSInput input = new JWSInput(idTokenString); diff --git a/adapters/oidc/js/src/main/resources/keycloak.js b/adapters/oidc/js/src/main/resources/keycloak.js index 563f7cacc5..2d8f4218c8 100755 --- a/adapters/oidc/js/src/main/resources/keycloak.js +++ b/adapters/oidc/js/src/main/resources/keycloak.js @@ -154,21 +154,34 @@ return; } else if (initOptions) { if (initOptions.token || initOptions.refreshToken) { - setToken(initOptions.token, initOptions.refreshToken, initOptions.idToken, false); - kc.timeSkew = initOptions.timeSkew || 0; + setToken(initOptions.token, initOptions.refreshToken, initOptions.idToken); if (loginIframe.enable) { setupCheckLoginIframe().success(function() { checkLoginIframe().success(function () { + kc.onAuthSuccess && kc.onAuthSuccess(); initPromise.setSuccess(); }).error(function () { + kc.onAuthError && kc.onAuthError(); if (initOptions.onLoad) { onLoad(); + } else { + initPromise.setError(); } }); }); } else { - initPromise.setSuccess(); + kc.updateToken(-1).success(function() { + kc.onAuthSuccess && kc.onAuthSuccess(); + initPromise.setSuccess(); + }).error(function() { + kc.onAuthError && kc.onAuthError(); + if (initOptions.onLoad) { + onLoad(); + } else { + initPromise.setError(); + } + }); } } else if (initOptions.onLoad) { onLoad(); @@ -349,11 +362,10 @@ throw 'Not authenticated'; } - var expiresIn = kc.tokenParsed['exp'] - (new Date().getTime() / 1000) + kc.timeSkew; + var expiresIn = kc.tokenParsed['exp'] - Math.ceil(new Date().getTime() / 1000) + kc.timeSkew; if (minValidity) { expiresIn -= minValidity; } - return expiresIn < 0; } @@ -368,7 +380,20 @@ minValidity = minValidity || 5; var exec = function() { - if (!kc.isTokenExpired(minValidity)) { + var refreshToken = false; + if (kc.timeSkew == -1) { + console.info('Skew ' + kc.timeSkew); + refreshToken = true; + console.info('[KEYCLOAK] Refreshing token: time skew not set'); + } else if (minValidity == -1) { + refreshToken = true; + console.info('[KEYCLOAK] Refreshing token: forced refresh'); + } else if (kc.isTokenExpired(minValidity)) { + refreshToken = true; + console.info('[KEYCLOAK] Refreshing token: token expired'); + } + + if (!refreshToken) { promise.setSuccess(false); } else { var params = 'grant_type=refresh_token&' + 'refresh_token=' + kc.refreshToken; @@ -380,6 +405,7 @@ var req = new XMLHttpRequest(); req.open('POST', url, true); req.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); + req.withCredentials = true; if (kc.clientId && kc.clientSecret) { req.setRequestHeader('Authorization', 'Basic ' + btoa(kc.clientId + ':' + kc.clientSecret)); @@ -392,18 +418,21 @@ req.onreadystatechange = function () { if (req.readyState == 4) { if (req.status == 200) { + console.info('[KEYCLOAK] Token refreshed'); + timeLocal = (timeLocal + new Date().getTime()) / 2; var tokenResponse = JSON.parse(req.responseText); - setToken(tokenResponse['access_token'], tokenResponse['refresh_token'], tokenResponse['id_token'], true); - kc.timeSkew = Math.floor(timeLocal / 1000) - kc.tokenParsed.iat; + setToken(tokenResponse['access_token'], tokenResponse['refresh_token'], tokenResponse['id_token'], timeLocal); kc.onAuthRefreshSuccess && kc.onAuthRefreshSuccess(); for (var p = refreshQueue.pop(); p != null; p = refreshQueue.pop()) { p.setSuccess(true); } } else { + console.warn('[KEYCLOAK] Failed to refresh token'); + kc.onAuthRefreshError && kc.onAuthRefreshError(); for (var p = refreshQueue.pop(); p != null; p = refreshQueue.pop()) { p.setError(true); @@ -433,7 +462,7 @@ kc.clearToken = function() { if (kc.token) { - setToken(null, null, null, true); + setToken(null, null, null); kc.onAuthLogout && kc.onAuthLogout(); if (kc.loginRequired) { kc.login(); @@ -514,18 +543,16 @@ function authSuccess(accessToken, refreshToken, idToken, fulfillPromise) { timeLocal = (timeLocal + new Date().getTime()) / 2; - setToken(accessToken, refreshToken, idToken, true); + setToken(accessToken, refreshToken, idToken, timeLocal); if ((kc.tokenParsed && kc.tokenParsed.nonce != oauth.storedNonce) || (kc.refreshTokenParsed && kc.refreshTokenParsed.nonce != oauth.storedNonce) || (kc.idTokenParsed && kc.idTokenParsed.nonce != oauth.storedNonce)) { - console.log('invalid nonce!'); + console.info('[KEYCLOAK] Invalid nonce, clearing token'); kc.clearToken(); promise && promise.setError(); } else { - kc.timeSkew = Math.floor(timeLocal / 1000) - kc.tokenParsed.iat; - if (fulfillPromise) { kc.onAuthSuccess && kc.onAuthSuccess(); promise && promise.setSuccess(); @@ -598,7 +625,7 @@ return promise.promise; } - function setToken(token, refreshToken, idToken, useTokenTime) { + function setToken(token, refreshToken, idToken, timeLocal) { if (kc.tokenTimeoutHandle) { clearTimeout(kc.tokenTimeoutHandle); kc.tokenTimeoutHandle = null; @@ -617,10 +644,24 @@ kc.realmAccess = kc.tokenParsed.realm_access; kc.resourceAccess = kc.tokenParsed.resource_access; + if (timeLocal) { + kc.timeSkew = Math.floor(timeLocal / 1000) - kc.tokenParsed.iat; + console.info('[KEYCLOAK] Estimated time difference between browser and server is ' + kc.timeSkew + ' seconds'); + } else { + kc.timeSkew = -1; + } + if (kc.onTokenExpired) { - var start = useTokenTime ? kc.tokenParsed.iat : (new Date().getTime() / 1000); - var expiresIn = kc.tokenParsed.exp - start; - kc.tokenTimeoutHandle = setTimeout(kc.onTokenExpired, expiresIn * 1000); + if (kc.timeSkew == -1) { + kc.onTokenExpired(); + } else { + var expiresIn = (kc.tokenParsed['exp'] - (new Date().getTime() / 1000) + kc.timeSkew) * 1000; + if (expiresIn <= 0) { + kc.onTokenExpired(); + } else { + kc.tokenTimeoutHandle = setTimeout(kc.onTokenExpired, expiresIn); + } + } } } else { @@ -1055,7 +1096,7 @@ if (!(this instanceof CookieStorage)) { return new CookieStorage(); } - + var cs = this; cs.get = function(state) { diff --git a/common/src/main/java/org/keycloak/common/Profile.java b/common/src/main/java/org/keycloak/common/Profile.java new file mode 100755 index 0000000000..ac16874f22 --- /dev/null +++ b/common/src/main/java/org/keycloak/common/Profile.java @@ -0,0 +1,69 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.keycloak.common; + +import java.io.File; +import java.io.FileInputStream; +import java.util.Properties; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class Profile { + + private enum ProfileValue { + PRODUCT, PREVIEW, COMMUNITY + } + + private static ProfileValue value = load(); + + static ProfileValue load() { + String profile = null; + try { + profile = System.getProperty("keycloak.profile"); + if (profile == null) { + String jbossServerConfigDir = System.getProperty("jboss.server.config.dir"); + if (jbossServerConfigDir != null) { + File file = new File(jbossServerConfigDir, "profile.properties"); + if (file.isFile()) { + Properties props = new Properties(); + props.load(new FileInputStream(file)); + profile = props.getProperty("profile"); + } + } + } + } catch (Exception e) { + } + + if (profile == null) { + return ProfileValue.valueOf(Version.DEFAULT_PROFILE.toUpperCase()); + } else { + return ProfileValue.valueOf(profile.toUpperCase()); + } + } + + public static String getName() { + return value.name().toLowerCase(); + } + + public static boolean isPreviewEnabled() { + return value.ordinal() >= ProfileValue.PREVIEW.ordinal(); + } + +} diff --git a/common/src/main/java/org/keycloak/common/Version.java b/common/src/main/java/org/keycloak/common/Version.java index 42ba52a816..862ccd2def 100755 --- a/common/src/main/java/org/keycloak/common/Version.java +++ b/common/src/main/java/org/keycloak/common/Version.java @@ -32,6 +32,7 @@ public class Version { public static String VERSION; public static String RESOURCES_VERSION; public static String BUILD_TIME; + public static String DEFAULT_PROFILE; static { Properties props = new Properties(); @@ -40,6 +41,7 @@ public class Version { props.load(is); Version.NAME = props.getProperty("name"); Version.NAME_HTML = props.getProperty("name-html"); + Version.DEFAULT_PROFILE = props.getProperty("default-profile"); Version.VERSION = props.getProperty("version"); Version.BUILD_TIME = props.getProperty("build-time"); Version.RESOURCES_VERSION = Version.VERSION.toLowerCase(); diff --git a/common/src/main/java/org/keycloak/common/util/ConcurrentMultivaluedHashMap.java b/common/src/main/java/org/keycloak/common/util/ConcurrentMultivaluedHashMap.java new file mode 100755 index 0000000000..56226e0241 --- /dev/null +++ b/common/src/main/java/org/keycloak/common/util/ConcurrentMultivaluedHashMap.java @@ -0,0 +1,102 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.keycloak.common.util; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +@SuppressWarnings("serial") +public class ConcurrentMultivaluedHashMap extends ConcurrentHashMap> +{ + public void putSingle(K key, V value) + { + List list = new CopyOnWriteArrayList<>(); + list.add(value); + put(key, list); + } + + public void addAll(K key, V... newValues) + { + for (V value : newValues) + { + add(key, value); + } + } + + public void addAll(K key, List valueList) + { + for (V value : valueList) + { + add(key, value); + } + } + + public void addFirst(K key, V value) + { + List list = get(key); + if (list == null) + { + add(key, value); + } + else + { + list.add(0, value); + } + } + public final void add(K key, V value) + { + getList(key).add(value); + } + + + public final void addMultiple(K key, Collection values) + { + getList(key).addAll(values); + } + + public V getFirst(K key) + { + List list = get(key); + return list == null ? null : list.get(0); + } + + public final List getList(K key) + { + List list = get(key); + if (list == null) + put(key, list = new CopyOnWriteArrayList()); + return list; + } + + public void addAll(ConcurrentMultivaluedHashMap other) + { + for (Entry> entry : other.entrySet()) + { + getList(entry.getKey()).addAll(entry.getValue()); + } + } + +} diff --git a/common/src/main/resources/keycloak-version.properties b/common/src/main/resources/keycloak-version.properties index 643b6de4af..f66e436852 100755 --- a/common/src/main/resources/keycloak-version.properties +++ b/common/src/main/resources/keycloak-version.properties @@ -18,4 +18,5 @@ name=${product.name} name-html=${product.name-html} version=${product.version} -build-time=${product.build-time} \ No newline at end of file +build-time=${product.build-time} +default-profile=${product.default-profile} \ No newline at end of file diff --git a/core/src/main/java/org/keycloak/RSATokenVerifier.java b/core/src/main/java/org/keycloak/RSATokenVerifier.java index 27a430102b..562261de5e 100755 --- a/core/src/main/java/org/keycloak/RSATokenVerifier.java +++ b/core/src/main/java/org/keycloak/RSATokenVerifier.java @@ -38,6 +38,12 @@ public class RSATokenVerifier { public static AccessToken verifyToken(String tokenString, PublicKey realmKey, String realmUrl, boolean checkActive, boolean checkTokenType) throws VerificationException { AccessToken token = toAccessToken(tokenString, realmKey); + tokenVerifications(token, realmUrl, checkActive, checkTokenType); + + return token; + } + + private static void tokenVerifications(AccessToken token, String realmUrl, boolean checkActive, boolean checkTokenType) throws VerificationException { String user = token.getSubject(); if (user == null) { throw new VerificationException("Token user was null."); @@ -60,9 +66,9 @@ public class RSATokenVerifier { throw new VerificationException("Token is not active."); } - return token; } + public static AccessToken toAccessToken(String tokenString, PublicKey realmKey) throws VerificationException { JWSInput input; try { @@ -81,6 +87,23 @@ public class RSATokenVerifier { return token; } + + public static AccessToken verifyToken(JWSInput input, PublicKey realmKey, String realmUrl, boolean checkActive, boolean checkTokenType) throws VerificationException { + if (!isPublicKeyValid(input, realmKey)) throw new VerificationException("Invalid token signature."); + + AccessToken token; + try { + token = input.readJsonContent(AccessToken.class); + } catch (JWSInputException e) { + throw new VerificationException("Couldn't parse token signature", e); + } + + tokenVerifications(token, realmUrl, checkActive, checkTokenType); + + return token; + } + + private static boolean isPublicKeyValid(JWSInput input, PublicKey realmKey) throws VerificationException { try { return RSAProvider.verify(input, realmKey); diff --git a/core/src/main/java/org/keycloak/constants/ServiceUrlConstants.java b/core/src/main/java/org/keycloak/constants/ServiceUrlConstants.java index a21f26284b..36b4f1d2d6 100755 --- a/core/src/main/java/org/keycloak/constants/ServiceUrlConstants.java +++ b/core/src/main/java/org/keycloak/constants/ServiceUrlConstants.java @@ -30,5 +30,6 @@ public interface ServiceUrlConstants { public static final String REALM_INFO_PATH = "/realms/{realm-name}"; public static final String CLIENTS_MANAGEMENT_REGISTER_NODE_PATH = "/realms/{realm-name}/clients-managements/register-node"; public static final String CLIENTS_MANAGEMENT_UNREGISTER_NODE_PATH = "/realms/{realm-name}/clients-managements/unregister-node"; + public static final String JWKS_URL = "/realms/{realm-name}/protocol/openid-connect/certs"; } diff --git a/core/src/main/java/org/keycloak/jose/jwk/JWKParser.java b/core/src/main/java/org/keycloak/jose/jwk/JWKParser.java index 7a31f72b6a..a20d253241 100755 --- a/core/src/main/java/org/keycloak/jose/jwk/JWKParser.java +++ b/core/src/main/java/org/keycloak/jose/jwk/JWKParser.java @@ -21,7 +21,6 @@ import com.fasterxml.jackson.core.type.TypeReference; import org.keycloak.common.util.Base64Url; import org.keycloak.util.JsonSerialization; -import java.io.InputStream; import java.math.BigInteger; import java.security.KeyFactory; import java.security.PublicKey; @@ -66,8 +65,8 @@ public class JWKParser { } public PublicKey toPublicKey() { - String algorithm = jwk.getKeyType(); - if (isAlgorithmSupported(algorithm)) { + String keyType = jwk.getKeyType(); + if (isKeyTypeSupported(keyType)) { BigInteger modulus = new BigInteger(1, Base64Url.decode(jwk.getOtherClaims().get(RSAPublicJWK.MODULUS).toString())); BigInteger publicExponent = new BigInteger(1, Base64Url.decode(jwk.getOtherClaims().get(RSAPublicJWK.PUBLIC_EXPONENT).toString())); @@ -77,12 +76,12 @@ public class JWKParser { throw new RuntimeException(e); } } else { - throw new RuntimeException("Unsupported algorithm " + algorithm); + throw new RuntimeException("Unsupported keyType " + keyType); } } - public boolean isAlgorithmSupported(String algorithm) { - return RSAPublicJWK.RSA.equals(algorithm); + public boolean isKeyTypeSupported(String keyType) { + return RSAPublicJWK.RSA.equals(keyType); } } diff --git a/core/src/main/java/org/keycloak/representations/adapters/config/AdapterConfig.java b/core/src/main/java/org/keycloak/representations/adapters/config/AdapterConfig.java index 91fb5f0e9c..c4818b45c7 100755 --- a/core/src/main/java/org/keycloak/representations/adapters/config/AdapterConfig.java +++ b/core/src/main/java/org/keycloak/representations/adapters/config/AdapterConfig.java @@ -36,7 +36,7 @@ import com.fasterxml.jackson.annotation.JsonPropertyOrder; "client-keystore", "client-keystore-password", "client-key-password", "always-refresh-token", "register-node-at-startup", "register-node-period", "token-store", "principal-attribute", - "proxy-url", "turn-off-change-session-id-on-login", "token-minimum-time-to-live", + "proxy-url", "turn-off-change-session-id-on-login", "token-minimum-time-to-live", "min-time-between-jwks-requests", "policy-enforcer" }) public class AdapterConfig extends BaseAdapterConfig { @@ -71,6 +71,8 @@ public class AdapterConfig extends BaseAdapterConfig { protected Boolean turnOffChangeSessionIdOnLogin; @JsonProperty("token-minimum-time-to-live") protected int tokenMinimumTimeToLive = 0; + @JsonProperty("min-time-between-jwks-requests") + protected int minTimeBetweenJwksRequests = 10; @JsonProperty("policy-enforcer") protected PolicyEnforcerConfig policyEnforcerConfig; @@ -216,4 +218,11 @@ public class AdapterConfig extends BaseAdapterConfig { this.tokenMinimumTimeToLive = tokenMinimumTimeToLive; } + public int getMinTimeBetweenJwksRequests() { + return minTimeBetweenJwksRequests; + } + + public void setMinTimeBetweenJwksRequests(int minTimeBetweenJwksRequests) { + this.minTimeBetweenJwksRequests = minTimeBetweenJwksRequests; + } } diff --git a/core/src/main/java/org/keycloak/representations/idm/IdentityProviderRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/IdentityProviderRepresentation.java index 524cbb39ce..2466f12e4f 100755 --- a/core/src/main/java/org/keycloak/representations/idm/IdentityProviderRepresentation.java +++ b/core/src/main/java/org/keycloak/representations/idm/IdentityProviderRepresentation.java @@ -123,10 +123,15 @@ public class IdentityProviderRepresentation { this.updateProfileFirstLoginMode = updateProfileFirstLoginMode; } + /** + * @deprecated Replaced by configuration option in identity provider authenticator + */ + @Deprecated public boolean isAuthenticateByDefault() { return authenticateByDefault; } + @Deprecated public void setAuthenticateByDefault(boolean authenticateByDefault) { this.authenticateByDefault = authenticateByDefault; } diff --git a/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java index 67c1678ad1..89e0c0181f 100755 --- a/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java +++ b/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java @@ -123,6 +123,8 @@ public class RealmRepresentation { protected String resetCredentialsFlow; protected String clientAuthenticationFlow; + protected Map attributes; + protected String keycloakVersion; @Deprecated @@ -864,4 +866,12 @@ public class RealmRepresentation { return identityProviders != null && !identityProviders.isEmpty(); } + public void setAttributes(Map attributes) { + this.attributes = attributes; + } + + public Map getAttributes() { + return attributes; + } + } diff --git a/core/src/main/java/org/keycloak/representations/info/ProfileInfoRepresentation.java b/core/src/main/java/org/keycloak/representations/info/ProfileInfoRepresentation.java new file mode 100644 index 0000000000..3c474d03ba --- /dev/null +++ b/core/src/main/java/org/keycloak/representations/info/ProfileInfoRepresentation.java @@ -0,0 +1,53 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.keycloak.representations.info; + +import org.keycloak.common.Profile; + +/** + * @author Stian Thorgersen + */ +public class ProfileInfoRepresentation { + + private String name; + private boolean previewEnabled; + + public static ProfileInfoRepresentation create() { + ProfileInfoRepresentation info = new ProfileInfoRepresentation(); + info.setName(Profile.getName()); + info.setPreviewEnabled(Profile.isPreviewEnabled()); + return info; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public boolean isPreviewEnabled() { + return previewEnabled; + } + + public void setPreviewEnabled(boolean previewEnabled) { + this.previewEnabled = previewEnabled; + } + +} diff --git a/core/src/main/java/org/keycloak/representations/info/ServerInfoRepresentation.java b/core/src/main/java/org/keycloak/representations/info/ServerInfoRepresentation.java index 59d400e485..8c98ce3468 100755 --- a/core/src/main/java/org/keycloak/representations/info/ServerInfoRepresentation.java +++ b/core/src/main/java/org/keycloak/representations/info/ServerInfoRepresentation.java @@ -32,6 +32,7 @@ public class ServerInfoRepresentation { private SystemInfoRepresentation systemInfo; private MemoryInfoRepresentation memoryInfo; + private ProfileInfoRepresentation profileInfo; private Map> themes; @@ -66,6 +67,14 @@ public class ServerInfoRepresentation { this.memoryInfo = memoryInfo; } + public ProfileInfoRepresentation getProfileInfo() { + return profileInfo; + } + + public void setProfileInfo(ProfileInfoRepresentation profileInfo) { + this.profileInfo = profileInfo; + } + public Map> getThemes() { return themes; } diff --git a/core/src/main/java/org/keycloak/util/JWKSUtils.java b/core/src/main/java/org/keycloak/util/JWKSUtils.java new file mode 100644 index 0000000000..72ffe91c86 --- /dev/null +++ b/core/src/main/java/org/keycloak/util/JWKSUtils.java @@ -0,0 +1,45 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.keycloak.util; + +import java.security.PublicKey; +import java.util.HashMap; +import java.util.Map; + +import org.keycloak.jose.jwk.JSONWebKeySet; +import org.keycloak.jose.jwk.JWK; +import org.keycloak.jose.jwk.JWKParser; + +/** + * @author Marek Posolda + */ +public class JWKSUtils { + + public static Map getKeysForUse(JSONWebKeySet keySet, JWK.Use requestedUse) { + Map result = new HashMap<>(); + + for (JWK jwk : keySet.getKeys()) { + JWKParser parser = JWKParser.create(jwk); + if (jwk.getPublicKeyUse().equals(requestedUse.asString()) && parser.isKeyTypeSupported(jwk.getKeyType())) { + result.put(jwk.getKeyId(), parser.toPublicKey()); + } + } + + return result; + } +} diff --git a/core/src/test/java/org/keycloak/JsonParserTest.java b/core/src/test/java/org/keycloak/JsonParserTest.java index e346fe6c05..965a13c863 100755 --- a/core/src/test/java/org/keycloak/JsonParserTest.java +++ b/core/src/test/java/org/keycloak/JsonParserTest.java @@ -17,17 +17,6 @@ package org.keycloak; -import java.io.IOException; -import java.io.InputStream; -import java.util.HashMap; -import java.util.Map; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import com.fasterxml.jackson.annotation.JsonAnyGetter; -import com.fasterxml.jackson.annotation.JsonAnySetter; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonUnwrapped; import org.junit.Assert; import org.junit.Test; import org.keycloak.representations.IDToken; @@ -36,6 +25,13 @@ import org.keycloak.representations.adapters.config.AdapterConfig; import org.keycloak.representations.oidc.OIDCClientRepresentation; import org.keycloak.util.JsonSerialization; +import java.io.IOException; +import java.io.InputStream; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + /** * @author Marek Posolda */ @@ -138,6 +134,18 @@ public class JsonParserTest { Assert.assertNull(clientRep.getJwks()); } + @Test + public void testReadOIDCClientRepWithPairwise() throws IOException { + String stringRep = "{\"subject_type\": \"pairwise\", \"jwks_uri\": \"https://op.certification.openid.net:60720/export/jwk_60720.json\", \"contacts\": [\"roland.hedberg@umu.se\"], \"application_type\": \"web\", \"grant_types\": [\"authorization_code\"], \"post_logout_redirect_uris\": [\"https://op.certification.openid.net:60720/logout\"], \"redirect_uris\": [\"https://op.certification.openid.net:60720/authz_cb\"], \"response_types\": [\"code\"], \"require_auth_time\": true, \"default_max_age\": 3600}"; + OIDCClientRepresentation clientRep = JsonSerialization.readValue(stringRep, OIDCClientRepresentation.class); + Assert.assertEquals("pairwise", clientRep.getSubjectType()); + Assert.assertTrue(clientRep.getRequireAuthTime()); + Assert.assertEquals(3600, clientRep.getDefaultMaxAge().intValue()); + Assert.assertEquals(1, clientRep.getRedirectUris().size()); + Assert.assertEquals("https://op.certification.openid.net:60720/authz_cb", clientRep.getRedirectUris().get(0)); + Assert.assertNull(clientRep.getJwks()); + } + @Test public void testReadOIDCClientRepWithJWKS() throws IOException { String stringRep = "{\"token_endpoint_auth_method\": \"private_key_jwt\", \"subject_type\": \"public\", \"jwks_uri\": null, \"jwks\": {\"keys\": [{\"use\": \"enc\", \"e\": \"AQAB\", \"d\": \"lZQv0_81euRLeUYU84Aodh0ar7ymDlzWP5NMra4Jklkb-lTBWkI-u4RMsPqGYyW3KHRoL_pgzZXSzQx8RLQfER6timRWb--NxMMKllZubByU3RqH2ooNuocJurspYiXkznPW1Mg9DaNXL0C2hwWPQHTeUVISpjgi5TCOV1ccWVyksFruya_VNL1CIByB-L0GL1rqbKv32cDwi2A3_jJa61cpzfLSIBe-lvCO6tuiDsR4qgJnUwnndQFwEI_4mLmD3iNWXrc8N-poleV8mBfMqBB5fWwy_ZTFCpmQ5AywGmctaik_wNhMoWuA4tUfY6_1LdKld-5Cjq55eLtuJjtvuQ\", \"n\": \"tx3Hjdbc19lkTiohbJrNj4jf2_90MEE122CRrwtFu6saDywKcG7Bi7w2FMAK2oTkuWfqhWRb5BEGmnSXdiCEPO5d-ytqP3nwlZXHaCDYscpP8bB4YLhvCn7R8Efw6gwQle24QPRP3lYoFeuUbDUq7GKA5SfaZUvWoeWjqyLIaBspKQsC26_Umx1E4IXLrMSL6nkRnrYcVZBAXrYCeTP1XtsV38_lZVJfHSaJaUy4PKaj3yvgm93EV2CXybPti7CCMXZ34VqqWiF64pQjZsPu3ZTr7ha_TTQq499-zYRQNDvIVsBDLQQIgrbctuGqj6lrXb31Jj3JIEYqH_4h5X9d0Q\", \"q\": \"1q-r-bmMFbIzrLK2U3elksZq8CqUqZxlSfkGMZuVkxgYMS-e4FPzEp2iirG-eO11aa0cpMMoBdTnVdGJ_ZUR93w0lGf9XnQAJqxP7eOsrUoiW4VWlWH4WfOiLgpO-pFtyTz_JksYYaotc_Z3Zy-Szw6a39IDbuYGy1qL-15oQuc\", \"p\": \"2lrYPppRbcQWu4LtWN6tOVUrtCOPv1eLTKTc7q8vCMcem1Ox5QFB7KnUtNZ5Ni7wnZUeVDfimNebtjNsGvDSrpgIlo9dEnFBQsQIkzZ2SkoYfgmF8hNdi6P-BfRjdgYouy4c6xAnGDgSMTip1YnPRyvbMaoYT9E_tEcBW5wOeoc\", \"kid\": \"a0\", \"kty\": \"RSA\"}, {\"use\": \"sig\", \"e\": \"AQAB\", \"d\": \"DodXDEtkovWWGsMEXYy_nEEMCWyROMOebCnCv0ey3i4M4bh2dmwqgz0e-IKQAFlGiMkidGL1lNbq0uFS04FbuRAR06dYw1cbrNbDdhrWFxKTd1L5D9p-x-gW-YDWhpI8rUGRa76JXkOSxZUbg09_QyUd99CXAHh-FXi_ZkIKD8hK6FrAs68qhLf8MNkUv63DTduw7QgeFfQivdopePxyGuMk5n8veqwsUZsklQkhNlTYQqeM1xb2698ZQcNYkl0OssEsSJKRjXt-LRPowKrdvTuTo2p--HMI0pIEeFs7H_u5OW3jihjvoFClGPynHQhgWmQzlQRvWRXh6FhDVqFeGQ\", \"n\": \"zfZzttF7HmnTYwSMPdxKs5AoczbNS2mOPz-tN1g4ljqI_F1DG8cgQDcN_VDufxoFGRERo2FK6WEN41LhbGEyP6uL6wW6Cy29qE9QZcvY5mXrncndRSOkNcMizvuEJes_fMYrmP_lPiC6kWiqItTk9QBWqJfiYKhCx9cSDXsBmJXn3KWQCVHvj1ANFWW0CWLMKlWN-_NMNLIWJN_pEAocTZMzxSFBK1b5_5J8ZS7hfWRF6MQmjsJcz2jzA21SQZNpre3kwnTGRSwo05sAS-TyeadDqQPWgbqX69UzcGq5irhzN8cpZ_JaTk3Y_uV6owanTZLVvCgdjaAnMYeZhb0KFw\", \"q\": \"5E5XKK5njT-zzRqqTeY2tgP9PJBACeaH_xQRHZ_1ydE7tVd7HdgdaEHfQ1jvKIHFkknWWOBAY1mlBc4YDirLShB_voShD8C-Hx3nF5sne5fleVfU-sZy6Za4B2U75PcE62oZgCPauOTAEm9Xuvrt5aMMovyzR8ecJZhm9bw7naU\", \"p\": \"5vJHCSM3H3q4RltYzENC9RyZZV8EUmpkv9moyguT5t-BUGA-T4W_FGIxzOPXRWOckIplKkoDKhavUeNmTZMCUcue0nkICSJpvNE4Nb2p5PZk_QqSdQNvCasQtdojEG0AmfVD85SU551CYxJdLdDFOqyK2entpMr8lhokem189As\", \"kid\": \"a1\", \"kty\": \"RSA\"}, {\"d\": \"S4_OufhLBgXFMgIDMI1zlVe2uCExpcEAQ80J_lXfS8I\", \"use\": \"sig\", \"crv\": \"P-256\", \"kty\": \"EC\", \"y\": \"DBdNyq30mXmUs_BIvKMqaTTNO7HDhCi0YiC8GciwNYk\", \"x\": \"cYwzBoyjRjxj334bRTqanONf7DUYK-6TgiuN0DixJAk\", \"kid\": \"a2\"}, {\"d\": \"33TnYgdJtWAiVosKqUnz0zSmvWTbsx5-6pceynW6Xck\", \"use\": \"enc\", \"crv\": \"P-256\", \"kty\": \"EC\", \"y\": \"Cula95Eix1Ia77St3OULe6-UKWs5I06nmdfUzhXUQTs\", \"x\": \"wk8HBVxNNzj1gJBxPmmx9XYW1L61ObBGzxpRa6_OqWU\", \"kid\": \"a3\"}]}, \"application_type\": \"web\", \"contacts\": [\"roland.hedberg@umu.se\"], \"post_logout_redirect_uris\": [\"https://op.certification.openid.net:60784/logout\"], \"redirect_uris\": [\"https://op.certification.openid.net:60784/authz_cb\"], \"response_types\": [\"code\"], \"require_auth_time\": true, \"grant_types\": [\"authorization_code\"], \"default_max_age\": 3600}"; diff --git a/dependencies/server-all/pom.xml b/dependencies/server-all/pom.xml index 809890a03d..8922532ad2 100755 --- a/dependencies/server-all/pom.xml +++ b/dependencies/server-all/pom.xml @@ -83,7 +83,6 @@ org.keycloak keycloak-kerberos-federation - org.keycloak @@ -110,6 +109,12 @@ + + + org.keycloak + keycloak-sssd-federation + + org.keycloak diff --git a/distribution/src-dist/assembly.xml b/distribution/adapters/fuse-adapter-zip/assembly.xml old mode 100755 new mode 100644 similarity index 67% rename from distribution/src-dist/assembly.xml rename to distribution/adapters/fuse-adapter-zip/assembly.xml index 5b27a8818c..10667933f7 --- a/distribution/src-dist/assembly.xml +++ b/distribution/adapters/fuse-adapter-zip/assembly.xml @@ -16,23 +16,21 @@ --> - src + fuse-adapter-dist zip + tar.gz - true + false - ../../ - - - **/.idea/** - **/.svn/** - **/target/** - **/*.iml - + ${project.build.directory}/system + + */** + + system - + \ No newline at end of file diff --git a/distribution/adapters/fuse-adapter-zip/pom.xml b/distribution/adapters/fuse-adapter-zip/pom.xml new file mode 100644 index 0000000000..c64972085d --- /dev/null +++ b/distribution/adapters/fuse-adapter-zip/pom.xml @@ -0,0 +1,173 @@ + + + + 4.0.0 + + keycloak-parent + org.keycloak + 2.2.0-SNAPSHOT + ../../../pom.xml + + + keycloak-fuse-adapter-dist + pom + Keycloak Fuse Adapter Distro + + + + + org.keycloak + keycloak-osgi-features + ${project.version} + xml + features + + + org.keycloak + keycloak-osgi-thirdparty + ${project.version} + + + org.bouncycastle + bcprov-jdk15on + + + org.bouncycastle + bcpkix-jdk15on + + + com.fasterxml.jackson.core + jackson-core + + + com.fasterxml.jackson.core + jackson-annotations + + + com.fasterxml.jackson.core + jackson-databind + + + com.fasterxml.jackson.module + jackson-module-jaxb-annotations + + + com.fasterxml.jackson.jaxrs + jackson-jaxrs-base + + + com.fasterxml.jackson.jaxrs + jackson-jaxrs-json-provider + + + org.jboss.logging + jboss-logging + + + org.keycloak + keycloak-common + + + org.keycloak + keycloak-core + + + org.keycloak + keycloak-authz-client + + + org.keycloak + keycloak-adapter-spi + + + org.keycloak + keycloak-adapter-core + + + org.keycloak + keycloak-osgi-adapter + + + org.keycloak + keycloak-jetty-adapter-spi + + + org.keycloak + keycloak-jetty-core + + + org.keycloak + keycloak-jetty81-adapter + + + org.keycloak + keycloak-osgi-jaas + ${project.version} + + + org.keycloak + keycloak-jetty92-adapter + + + + + + + org.apache.maven.plugins + maven-dependency-plugin + + + copy-dependencies + prepare-package + + copy-dependencies + + + ${project.build.directory}/system + false + false + true + true + + + + + + maven-assembly-plugin + + + assemble + package + + single + + + + assembly.xml + + target + target/assembly/work + false + + + + + + + \ No newline at end of file diff --git a/distribution/adapters/pom.xml b/distribution/adapters/pom.xml index cd72e9a907..23d5135ff7 100755 --- a/distribution/adapters/pom.xml +++ b/distribution/adapters/pom.xml @@ -32,6 +32,7 @@ as7-eap6-adapter + fuse-adapter-zip jetty81-adapter-zip jetty91-adapter-zip jetty92-adapter-zip diff --git a/distribution/demo-dist/src/main/xslt/standalone.xsl b/distribution/demo-dist/src/main/xslt/standalone.xsl index 855efc0c56..ca45244efe 100755 --- a/distribution/demo-dist/src/main/xslt/standalone.xsl +++ b/distribution/demo-dist/src/main/xslt/standalone.xsl @@ -42,7 +42,7 @@ - + jdbc:h2:${jboss.server.data.dir}/keycloak;AUTO_SERVER=TRUE h2 diff --git a/distribution/downloads/pom.xml b/distribution/downloads/pom.xml index e1356d689a..e18654557e 100755 --- a/distribution/downloads/pom.xml +++ b/distribution/downloads/pom.xml @@ -112,12 +112,6 @@ zip keycloak-examples-${project.version}.zip - - org.keycloak - keycloak-src-dist - zip - keycloak-src-${project.version}.zip - target/${project.version} @@ -262,6 +256,17 @@ keycloak-wildfly-adapter-dist tar.gz + + + org.keycloak + keycloak-fuse-adapter-dist + zip + + + org.keycloak + keycloak-fuse-adapter-dist + tar.gz + target/${project.version}/adapters/keycloak-oidc diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/content/bin/federation-sssd-setup.sh b/distribution/feature-packs/server-feature-pack/src/main/resources/content/bin/federation-sssd-setup.sh new file mode 100644 index 0000000000..6a0eae2130 --- /dev/null +++ b/distribution/feature-packs/server-feature-pack/src/main/resources/content/bin/federation-sssd-setup.sh @@ -0,0 +1,31 @@ +#!/bin/sh + +# Setup for SSSD +SSSD_FILE="/etc/sssd/sssd.conf" + +if [ -f "$SSSD_FILE" ]; +then + sed -i '/ldap_tls_cacert/a ldap_user_extra_attrs = mail:mail, sn:sn, givenname:givenname, telephoneNumber:telephoneNumber' $SSSD_FILE + sed -i 's/nss, sudo, pam/nss, sudo, pam, ifp/' $SSSD_FILE + sed -i '/\[ifp\]/a allowed_uids = root\nuser_attributes = +mail, +telephoneNumber, +givenname, +sn' $SSSD_FILE + systemctl restart sssd +else + echo "Please make sure you have $SSSD_FILE into your system! Aborting." + exit 1 +fi + +# Setup for PAM +PAM_FILE="/etc/pam.d/keycloak" + +if [ ! -f "$PAM_FILE" ]; +then +cat < $PAM_FILE + auth required pam_sss.so + account required pam_sss.so +EOF +else + echo "$PAM_FILE already exists. Skipping it..." + exit 0 +fi + + diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-model-jpa/main/module.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-model-jpa/main/module.xml index 5373067447..94c80a2a96 100755 --- a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-model-jpa/main/module.xml +++ b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-model-jpa/main/module.xml @@ -25,6 +25,7 @@ + diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-server-spi/main/module.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-server-spi/main/module.xml index 95f34b6943..8b5632e30a 100755 --- a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-server-spi/main/module.xml +++ b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-server-spi/main/module.xml @@ -33,5 +33,6 @@ + diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-services/main/module.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-services/main/module.xml index de03ed8e8b..8cf1cde89b 100755 --- a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-services/main/module.xml +++ b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-services/main/module.xml @@ -35,6 +35,7 @@ + diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-sssd-federation/main/module.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-sssd-federation/main/module.xml new file mode 100644 index 0000000000..ad16f3c4dd --- /dev/null +++ b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-sssd-federation/main/module.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/distribution/pom.xml b/distribution/pom.xml index 74e0e402a2..746d5aeae6 100755 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -40,7 +40,6 @@ proxy-dist server-dist server-overlay - src-dist feature-packs diff --git a/distribution/src-dist/pom.xml b/distribution/src-dist/pom.xml deleted file mode 100755 index c1842270eb..0000000000 --- a/distribution/src-dist/pom.xml +++ /dev/null @@ -1,69 +0,0 @@ - - - - 4.0.0 - - keycloak-distribution-parent - org.keycloak - 2.2.0-SNAPSHOT - - - keycloak-src-dist - pom - Keycloak Source Distribution - - - - keycloak-src-${project.version} - - - org.apache.maven.plugins - maven-deploy-plugin - - true - - - - maven-assembly-plugin - - - assemble - package - - single - - - - assembly.xml - - - target - - - target/assembly/work - - false - - - - - - - - diff --git a/examples/authz/hello-world-authz-service/src/main/webapp/WEB-INF/keycloak.json b/examples/authz/hello-world-authz-service/src/main/webapp/WEB-INF/keycloak.json index a492837299..b5a1bbf797 100644 --- a/examples/authz/hello-world-authz-service/src/main/webapp/WEB-INF/keycloak.json +++ b/examples/authz/hello-world-authz-service/src/main/webapp/WEB-INF/keycloak.json @@ -1,6 +1,5 @@ { "realm": "hello-world-authz", - "realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB", "auth-server-url": "http://localhost:8080/auth", "ssl-required": "external", "resource": "hello-world-authz-service", diff --git a/examples/authz/hello-world/src/main/java/org/keycloak/authz/helloworld/AuthorizationClientExample.java b/examples/authz/hello-world/src/main/java/org/keycloak/authz/helloworld/AuthorizationClientExample.java index 887a461057..493637aeaa 100644 --- a/examples/authz/hello-world/src/main/java/org/keycloak/authz/helloworld/AuthorizationClientExample.java +++ b/examples/authz/hello-world/src/main/java/org/keycloak/authz/helloworld/AuthorizationClientExample.java @@ -43,7 +43,7 @@ public class AuthorizationClientExample { } private static void introspectRequestingPartyToken() { - // create a new instance based on the configuration define at keycloak-authz.json + // create a new instance based on the configuration defined in keycloak-authz.json AuthzClient authzClient = AuthzClient.create(); // query the server for a resource with a given name @@ -51,8 +51,9 @@ public class AuthorizationClientExample { .resource() .findByFilter("name=Default Resource"); - // obtian a Entitlement API Token in order to get access to the Entitlement API. - // this token is just an access token issued to a client on behalf of an user with a scope kc_entitlement + // obtain an Entitlement API Token in order to get access to the Entitlement API. + // this token is just an access token issued to a client on behalf of an user + // with a scope = kc_entitlement String eat = getEntitlementAPIToken(authzClient); // create an entitlement request @@ -63,7 +64,8 @@ public class AuthorizationClientExample { request.addPermission(permission); - // send the entitlement request to the server in order to obtain a RPT with all permissions granted to the user + // send the entitlement request to the server in order to + // obtain an RPT with all permissions granted to the user EntitlementResponse response = authzClient.entitlement(eat).get("hello-world-authz-service", request); String rpt = response.getRpt(); @@ -79,7 +81,7 @@ public class AuthorizationClientExample { } private static void createResource() { - // create a new instance based on the configuration define at keycloak-authz.json + // create a new instance based on the configuration defined in keycloak-authz.json AuthzClient authzClient = AuthzClient.create(); // create a new resource representation with the information we want @@ -111,8 +113,9 @@ public class AuthorizationClientExample { // create a new instance based on the configuration define at keycloak-authz.json AuthzClient authzClient = AuthzClient.create(); - // obtian a Entitlement API Token in order to get access to the Entitlement API. - // this token is just an access token issued to a client on behalf of an user with a scope kc_entitlement + // obtain an Entitlement API Token in order to get access to the Entitlement API. + // this token is just an access token issued to a client on behalf of an user + // with a scope = kc_entitlement String eat = getEntitlementAPIToken(authzClient); // create an entitlement request @@ -123,7 +126,8 @@ public class AuthorizationClientExample { request.addPermission(permission); - // send the entitlement request to the server in order to obtain a RPT with all permissions granted to the user + // send the entitlement request to the server in order to obtain a RPT + // with all permissions granted to the user EntitlementResponse response = authzClient.entitlement(eat).get("hello-world-authz-service", request); String rpt = response.getRpt(); @@ -133,7 +137,7 @@ public class AuthorizationClientExample { } private static void obtainAllEntitlements() { - // create a new instance based on the configuration define at keycloak-authz.json + // create a new instance based on the configuration defined in keycloak-authz.json AuthzClient authzClient = AuthzClient.create(); // obtian a Entitlement API Token in order to get access to the Entitlement API. diff --git a/examples/authz/photoz/photoz-html5-client/src/main/webapp/keycloak.json b/examples/authz/photoz/photoz-html5-client/src/main/webapp/keycloak.json index c1dee24574..affafdd82c 100644 --- a/examples/authz/photoz/photoz-html5-client/src/main/webapp/keycloak.json +++ b/examples/authz/photoz/photoz-html5-client/src/main/webapp/keycloak.json @@ -1,6 +1,5 @@ { "realm": "photoz", - "realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB", "auth-server-url" : "http://localhost:8080/auth", "ssl-required" : "external", "resource" : "photoz-html5-client", diff --git a/examples/authz/photoz/photoz-restful-api/src/main/webapp/WEB-INF/keycloak.json b/examples/authz/photoz/photoz-restful-api/src/main/webapp/WEB-INF/keycloak.json index 6849d0771b..9e06730fe3 100644 --- a/examples/authz/photoz/photoz-restful-api/src/main/webapp/WEB-INF/keycloak.json +++ b/examples/authz/photoz/photoz-restful-api/src/main/webapp/WEB-INF/keycloak.json @@ -1,6 +1,5 @@ { "realm": "photoz", - "realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB", "auth-server-url": "http://localhost:8080/auth", "ssl-required": "external", "resource": "photoz-restful-api", diff --git a/examples/authz/servlet-authz/src/main/webapp/WEB-INF/keycloak.json b/examples/authz/servlet-authz/src/main/webapp/WEB-INF/keycloak.json index eaffea8d19..f6b9c90927 100644 --- a/examples/authz/servlet-authz/src/main/webapp/WEB-INF/keycloak.json +++ b/examples/authz/servlet-authz/src/main/webapp/WEB-INF/keycloak.json @@ -1,6 +1,5 @@ { "realm": "servlet-authz", - "realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB", "auth-server-url" : "http://localhost:8080/auth", "ssl-required" : "external", "resource" : "servlet-authz-app", diff --git a/examples/basic-auth/src/main/webapp/WEB-INF/keycloak.json b/examples/basic-auth/src/main/webapp/WEB-INF/keycloak.json index 4502199d70..9da7ed42e5 100644 --- a/examples/basic-auth/src/main/webapp/WEB-INF/keycloak.json +++ b/examples/basic-auth/src/main/webapp/WEB-INF/keycloak.json @@ -1,7 +1,6 @@ { "realm" : "basic-auth", "resource" : "basic-auth-service", - "realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB", "auth-server-url": "/auth", "ssl-required" : "external", "enable-basic-auth" : "true", diff --git a/examples/broker/facebook-authentication/src/main/webapp/keycloak.json b/examples/broker/facebook-authentication/src/main/webapp/keycloak.json index 55446affdb..17743f663e 100644 --- a/examples/broker/facebook-authentication/src/main/webapp/keycloak.json +++ b/examples/broker/facebook-authentication/src/main/webapp/keycloak.json @@ -1,7 +1,6 @@ { "realm" : "facebook-identity-provider-realm", "resource" : "facebook-authentication", - "realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB", "auth-server-url": "/auth", "ssl-required" : "external", "public-client" : true diff --git a/examples/broker/google-authentication/src/main/webapp/keycloak.json b/examples/broker/google-authentication/src/main/webapp/keycloak.json index 93c25b4497..f05da2ff5e 100644 --- a/examples/broker/google-authentication/src/main/webapp/keycloak.json +++ b/examples/broker/google-authentication/src/main/webapp/keycloak.json @@ -1,7 +1,6 @@ { "realm" : "google-identity-provider-realm", "resource" : "google-authentication", - "realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB", "auth-server-url": "/auth", "ssl-required" : "external", "public-client" : true diff --git a/examples/broker/twitter-authentication/src/main/webapp/keycloak.json b/examples/broker/twitter-authentication/src/main/webapp/keycloak.json index 7243636390..2f745149d9 100644 --- a/examples/broker/twitter-authentication/src/main/webapp/keycloak.json +++ b/examples/broker/twitter-authentication/src/main/webapp/keycloak.json @@ -1,7 +1,6 @@ { "realm" : "twitter-identity-provider-realm", "resource" : "twitter-authentication", - "realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB", "auth-server-url": "/auth", "ssl-required" : "external", "public-client" : true diff --git a/examples/cors/angular-product-app/src/main/webapp/keycloak.json b/examples/cors/angular-product-app/src/main/webapp/keycloak.json index 40b35a1953..d68545945d 100755 --- a/examples/cors/angular-product-app/src/main/webapp/keycloak.json +++ b/examples/cors/angular-product-app/src/main/webapp/keycloak.json @@ -1,6 +1,5 @@ { "realm" : "cors", - "realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB", "auth-server-url" : "http://localhost-auth:8080/auth", "ssl-required" : "external", "resource" : "angular-cors-product", diff --git a/examples/cors/database-service/src/main/webapp/WEB-INF/keycloak.json b/examples/cors/database-service/src/main/webapp/WEB-INF/keycloak.json index 265049f22c..61da4087a3 100755 --- a/examples/cors/database-service/src/main/webapp/WEB-INF/keycloak.json +++ b/examples/cors/database-service/src/main/webapp/WEB-INF/keycloak.json @@ -1,7 +1,6 @@ { "realm" : "cors", "resource" : "cors-database-service", - "realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB", "auth-server-url": "http://localhost-auth:8080/auth", "bearer-only" : true, "ssl-required": "external", diff --git a/examples/demo-template/angular-product-app/src/main/webapp/keycloak.json b/examples/demo-template/angular-product-app/src/main/webapp/keycloak.json index 72ecb5be56..174053e4a1 100755 --- a/examples/demo-template/angular-product-app/src/main/webapp/keycloak.json +++ b/examples/demo-template/angular-product-app/src/main/webapp/keycloak.json @@ -1,6 +1,5 @@ { "realm" : "demo", - "realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB", "auth-server-url" : "/auth", "ssl-required" : "external", "resource" : "angular-product", diff --git a/examples/demo-template/angular2-product-app/pom.xml b/examples/demo-template/angular2-product-app/pom.xml index 7eecc429dd..d461196042 100644 --- a/examples/demo-template/angular2-product-app/pom.xml +++ b/examples/demo-template/angular2-product-app/pom.xml @@ -21,7 +21,7 @@ keycloak-examples-demo-parent org.keycloak - + 2.2.0-SNAPSHOT 4.0.0 diff --git a/examples/demo-template/angular2-product-app/src/main/webapp/.gitignore b/examples/demo-template/angular2-product-app/src/main/webapp/.gitignore new file mode 100644 index 0000000000..1c790facbc --- /dev/null +++ b/examples/demo-template/angular2-product-app/src/main/webapp/.gitignore @@ -0,0 +1,4 @@ +app/*.js +app/*.js.map +node_modules +typings diff --git a/examples/demo-template/angular2-product-app/src/main/webapp/app/app.component.ts b/examples/demo-template/angular2-product-app/src/main/webapp/app/app.component.ts new file mode 100644 index 0000000000..d43e25740d --- /dev/null +++ b/examples/demo-template/angular2-product-app/src/main/webapp/app/app.component.ts @@ -0,0 +1,67 @@ +import { Component } from '@angular/core'; +import { Http, Headers, RequestOptions, Response } from '@angular/http'; + +import { Observable } from 'rxjs/Observable'; +import 'rxjs/add/operator/map'; + +import { KeycloakService } from './keycloak.service'; + +@Component({ + selector: 'my-app', + template: ` +
+
+

Angular2 Product (Beta)

+

Products

+ + + + + + + + + + + + + +
Product Listing
{{p}}
+
+
+` +}) +export class AppComponent { + products: string[] = []; + + constructor(private http: Http, private kc: KeycloakService) {} + + logout() { + this.kc.logout(); + } + + reloadData() { + //angular dont have http interceptor yet + + this.kc.getToken() + .then(token => { + let headers = new Headers({ + 'Accept': 'application/json', + 'Authorization': 'Bearer ' + token + }); + + let options = new RequestOptions({ headers }); + + this.http.get('/database/products', options) + .map(res => res.json()) + .subscribe(prods => this.products = prods, + error => console.log(error)); + }) + .catch(error => console.log(error)); + } + + private handleError(error: Response) { + console.error(error); + return Observable.throw(error.json().error || 'Server error'); + } +} diff --git a/examples/demo-template/angular2-product-app/src/main/webapp/app/app.module.ts b/examples/demo-template/angular2-product-app/src/main/webapp/app/app.module.ts new file mode 100644 index 0000000000..f345fa3ea6 --- /dev/null +++ b/examples/demo-template/angular2-product-app/src/main/webapp/app/app.module.ts @@ -0,0 +1,20 @@ +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +import { HttpModule } from '@angular/http'; +import { KeycloakService } from './keycloak.service'; +import { AppComponent } from './app.component'; + +@NgModule({ + imports: [ + BrowserModule, + HttpModule + ], + declarations: [ + AppComponent + ], + providers: [ + KeycloakService, + ], + bootstrap: [ AppComponent ] +}) +export class AppModule {} diff --git a/examples/demo-template/angular2-product-app/src/main/webapp/app/app.ts b/examples/demo-template/angular2-product-app/src/main/webapp/app/app.ts deleted file mode 100644 index 8630f006ea..0000000000 --- a/examples/demo-template/angular2-product-app/src/main/webapp/app/app.ts +++ /dev/null @@ -1,78 +0,0 @@ -import {Http, Headers, - RequestOptions, Response} from 'angular2/http'; -import {Component} from 'angular2/core'; -import {Observable} from 'rxjs/Observable'; -import {KeycloakService} from './keycloak'; - - - - -@Component({ - selector: 'my-app', - template: -` -
-
-

Angular2 Product (Beta)

-

Products

- - - - - - - - - - - - - - -
Product Listing
{{p}}
-
-
-` -}) -export class AppComponent { - - constructor(private _kc:KeycloakService, private http:Http){ } - - products : string[] = []; - - logout(){ - this._kc.logout(); - } - - reloadData() { - //angular dont have http interceptor yet - - this._kc.getToken().then( - token=>{ - let headers = new Headers({ - 'Accept': 'application/json', - 'Authorization': 'Bearer ' + token - }); - - let options = new RequestOptions({ headers: headers }); - - this.http.get('/database/products', options) - .map(res => res.json()) - .subscribe( - prods => this.products = prods, - error => console.log(error)); - - }, - error=>{ - console.log(error); - } - ); - - } - - private handleError (error: Response) { - console.error(error); - return Observable.throw(error.json().error || 'Server error'); - } - -} diff --git a/examples/demo-template/angular2-product-app/src/main/webapp/app/keycloak.service.ts b/examples/demo-template/angular2-product-app/src/main/webapp/app/keycloak.service.ts new file mode 100644 index 0000000000..33fc28357e --- /dev/null +++ b/examples/demo-template/angular2-product-app/src/main/webapp/app/keycloak.service.ts @@ -0,0 +1,48 @@ +import { Injectable } from '@angular/core'; + +declare var Keycloak: any; + +@Injectable() +export class KeycloakService { + static auth: any = {}; + + static init(): Promise { + let keycloakAuth: any = new Keycloak('keycloak.json'); + KeycloakService.auth.loggedIn = false; + + return new Promise((resolve, reject) => { + keycloakAuth.init({ onLoad: 'login-required' }) + .success(() => { + KeycloakService.auth.loggedIn = true; + KeycloakService.auth.authz = keycloakAuth; + KeycloakService.auth.logoutUrl = keycloakAuth.authServerUrl + "/realms/demo/protocol/openid-connect/logout?redirect_uri=/angular2-product/index.html"; + resolve(); + }) + .error(() => { + reject(); + }); + }); + } + + logout() { + console.log('*** LOGOUT'); + KeycloakService.auth.loggedIn = false; + KeycloakService.auth.authz = null; + + window.location.href = KeycloakService.auth.logoutUrl; + } + + getToken(): Promise { + return new Promise((resolve, reject) => { + if (KeycloakService.auth.authz.token) { + KeycloakService.auth.authz.updateToken(5) + .success(() => { + resolve(KeycloakService.auth.authz.token); + }) + .error(() => { + reject('Failed to refresh token'); + }); + } + }); + } +} diff --git a/examples/demo-template/angular2-product-app/src/main/webapp/app/keycloak.ts b/examples/demo-template/angular2-product-app/src/main/webapp/app/keycloak.ts deleted file mode 100644 index 64242ae932..0000000000 --- a/examples/demo-template/angular2-product-app/src/main/webapp/app/keycloak.ts +++ /dev/null @@ -1,49 +0,0 @@ -import {Injectable} from 'angular2/core'; - - -declare var Keycloak: any; - -@Injectable() -export class KeycloakService { - - static auth : any = {}; - - static init() : Promise{ - let keycloakAuth : any = new Keycloak('keycloak.json'); - KeycloakService.auth.loggedIn = false; - - return new Promise((resolve,reject)=>{ - keycloakAuth.init({ onLoad: 'login-required' }) - .success( () => { - KeycloakService.auth.loggedIn = true; - KeycloakService.auth.authz = keycloakAuth; - KeycloakService.auth.logoutUrl = keycloakAuth.authServerUrl + "/realms/demo/tokens/logout?redirect_uri=/angular2-product/index.html"; - resolve(null); - }) - .error(()=> { - reject(null); - }); - }); - } - - logout(){ - console.log('*** LOGOUT'); - KeycloakService.auth.loggedIn = false; - KeycloakService.auth.authz = null; - - window.location.href = KeycloakService.auth.logoutUrl; - } - - getToken(): Promise{ - return new Promise((resolve,reject)=>{ - if (KeycloakService.auth.authz.token) { - KeycloakService.auth.authz.updateToken(5).success(function() { - resolve(KeycloakService.auth.authz.token); - }) - .error(function() { - reject('Failed to refresh token'); - }); - } - }); - } -} diff --git a/examples/demo-template/angular2-product-app/src/main/webapp/app/main.ts b/examples/demo-template/angular2-product-app/src/main/webapp/app/main.ts index 73613b2b92..6bf99bfc91 100644 --- a/examples/demo-template/angular2-product-app/src/main/webapp/app/main.ts +++ b/examples/demo-template/angular2-product-app/src/main/webapp/app/main.ts @@ -1,14 +1,11 @@ -import 'rxjs/Rx'; -import {bootstrap} from 'angular2/platform/browser'; -import {HTTP_BINDINGS} from 'angular2/http'; -import {KeycloakService} from './keycloak'; -import {AppComponent} from './app'; +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; +import { AppModule } from './app.module'; -KeycloakService.init().then( - o=>{ - bootstrap(AppComponent,[HTTP_BINDINGS, KeycloakService]); - }, - x=>{ - window.location.reload(); - } -); \ No newline at end of file +import {KeycloakService} from './keycloak.service'; + +KeycloakService.init() + .then(() => { + const platform = platformBrowserDynamic(); + platform.bootstrapModule(AppModule); + }) + .catch(() => window.location.reload()); diff --git a/examples/demo-template/angular2-product-app/src/main/webapp/index.html b/examples/demo-template/angular2-product-app/src/main/webapp/index.html index 2da600c335..1edeb56477 100644 --- a/examples/demo-template/angular2-product-app/src/main/webapp/index.html +++ b/examples/demo-template/angular2-product-app/src/main/webapp/index.html @@ -2,48 +2,23 @@ Angular 2 QuickStart + - - - - - - + + + + + + + + + + - Loading... - - - - - - - - - - - - - - - - - - - - diff --git a/examples/demo-template/angular2-product-app/src/main/webapp/keycloak.json b/examples/demo-template/angular2-product-app/src/main/webapp/keycloak.json index ddd1f2ec81..87a2ad64ef 100644 --- a/examples/demo-template/angular2-product-app/src/main/webapp/keycloak.json +++ b/examples/demo-template/angular2-product-app/src/main/webapp/keycloak.json @@ -1,6 +1,5 @@ { "realm": "demo", - "realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB", "auth-server-url": "/auth", "ssl-required": "external", "resource": "angular2-product", diff --git a/examples/demo-template/angular2-product-app/src/main/webapp/package.json b/examples/demo-template/angular2-product-app/src/main/webapp/package.json index f66b44c110..5bd783b03e 100644 --- a/examples/demo-template/angular2-product-app/src/main/webapp/package.json +++ b/examples/demo-template/angular2-product-app/src/main/webapp/package.json @@ -2,24 +2,36 @@ "name": "angular2-product-app", "version": "1.0.0", "scripts": { + "start": "tsc && concurrently \"npm run tsc:w\" \"npm run lite\" ", + "lite": "lite-server", + "postinstall": "typings install", "tsc": "tsc", "tsc:w": "tsc -w", - "lite": "lite-server", - "start": "concurrent \"npm run tsc:w\" \"npm run lite\" " + "typings": "typings" }, "license": "ISC", "dependencies": { - "angular2": "2.0.0-beta.3", - "systemjs": "0.19.6", - "es6-promise": "^3.0.2", - "es6-shim": "^0.33.3", - "reflect-metadata": "0.1.2", - "rxjs": "5.0.0-beta.0", - "zone.js": "0.5.11" + "@angular/common": "2.0.0", + "@angular/compiler": "2.0.0", + "@angular/core": "2.0.0", + "@angular/forms": "2.0.0", + "@angular/http": "2.0.0", + "@angular/platform-browser": "2.0.0", + "@angular/platform-browser-dynamic": "2.0.0", + "@angular/router": "3.0.0", + "@angular/upgrade": "2.0.0", + "angular2-in-memory-web-api": "0.0.20", + "bootstrap": "^3.3.6", + "core-js": "^2.4.1", + "reflect-metadata": "^0.1.3", + "rxjs": "5.0.0-beta.12", + "systemjs": "0.19.27", + "zone.js": "^0.6.21" }, "devDependencies": { - "concurrently": "^1.0.0", - "lite-server": "^2.0.1", - "typescript": "^1.7.5" + "concurrently": "^2.2.0", + "lite-server": "^2.2.2", + "typescript": "^2.0.2", + "typings": "^1.3.2" } } diff --git a/examples/demo-template/angular2-product-app/src/main/webapp/systemjs.config.js b/examples/demo-template/angular2-product-app/src/main/webapp/systemjs.config.js new file mode 100644 index 0000000000..de199e68ce --- /dev/null +++ b/examples/demo-template/angular2-product-app/src/main/webapp/systemjs.config.js @@ -0,0 +1,43 @@ +/** + * System configuration for Angular 2 samples + * Adjust as necessary for your application needs. + */ +(function (global) { + System.config({ + paths: { + // paths serve as alias + 'npm:': 'node_modules/' + }, + // map tells the System loader where to look for things + map: { + // our app is within the app folder + app: 'app', + // angular bundles + '@angular/core': 'npm:@angular/core/bundles/core.umd.js', + '@angular/common': 'npm:@angular/common/bundles/common.umd.js', + '@angular/compiler': 'npm:@angular/compiler/bundles/compiler.umd.js', + '@angular/platform-browser': 'npm:@angular/platform-browser/bundles/platform-browser.umd.js', + '@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js', + '@angular/http': 'npm:@angular/http/bundles/http.umd.js', + '@angular/router': 'npm:@angular/router/bundles/router.umd.js', + '@angular/forms': 'npm:@angular/forms/bundles/forms.umd.js', + // other libraries + 'rxjs': 'npm:rxjs', + 'angular2-in-memory-web-api': 'npm:angular2-in-memory-web-api', + }, + // packages tells the System loader how to load when no filename and/or no extension + packages: { + app: { + main: './main.js', + defaultExtension: 'js' + }, + rxjs: { + defaultExtension: 'js' + }, + 'angular2-in-memory-web-api': { + main: './index.js', + defaultExtension: 'js' + } + } + }); +})(this); diff --git a/examples/demo-template/angular2-product-app/src/main/webapp/tsconfig.json b/examples/demo-template/angular2-product-app/src/main/webapp/tsconfig.json index 52c77a54a4..e6a6eac11d 100644 --- a/examples/demo-template/angular2-product-app/src/main/webapp/tsconfig.json +++ b/examples/demo-template/angular2-product-app/src/main/webapp/tsconfig.json @@ -1,15 +1,12 @@ { "compilerOptions": { "target": "es5", - "module": "system", + "module": "commonjs", "moduleResolution": "node", - "sourceMap": false, + "sourceMap": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "removeComments": false, "noImplicitAny": false - }, - "exclude": [ - "node_modules" - ] -} \ No newline at end of file + } +} diff --git a/examples/demo-template/angular2-product-app/src/main/webapp/typings.json b/examples/demo-template/angular2-product-app/src/main/webapp/typings.json new file mode 100644 index 0000000000..7da31ca0af --- /dev/null +++ b/examples/demo-template/angular2-product-app/src/main/webapp/typings.json @@ -0,0 +1,7 @@ +{ + "globalDependencies": { + "core-js": "registry:dt/core-js#0.0.0+20160725163759", + "jasmine": "registry:dt/jasmine#2.2.0+20160621224255", + "node": "registry:dt/node#6.0.0+20160909174046" + } +} diff --git a/examples/demo-template/customer-app-cli/src/main/resources/META-INF/keycloak.json b/examples/demo-template/customer-app-cli/src/main/resources/META-INF/keycloak.json index 51c8775c99..1c61433813 100644 --- a/examples/demo-template/customer-app-cli/src/main/resources/META-INF/keycloak.json +++ b/examples/demo-template/customer-app-cli/src/main/resources/META-INF/keycloak.json @@ -1,6 +1,5 @@ { "realm" : "demo", - "realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB", "auth-server-url" : "http://localhost:8080/auth", "ssl-required" : "external", "resource" : "customer-portal-cli", diff --git a/examples/demo-template/customer-app-filter/src/main/webapp/WEB-INF/keycloak.json b/examples/demo-template/customer-app-filter/src/main/webapp/WEB-INF/keycloak.json index 14e56f543b..3766330ece 100755 --- a/examples/demo-template/customer-app-filter/src/main/webapp/WEB-INF/keycloak.json +++ b/examples/demo-template/customer-app-filter/src/main/webapp/WEB-INF/keycloak.json @@ -1,7 +1,6 @@ { "realm": "demo", "resource": "customer-portal-filter", - "realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB", "auth-server-url": "/auth", "ssl-required" : "external", "expose-token": true, diff --git a/examples/demo-template/customer-app-js/src/main/webapp/keycloak.json b/examples/demo-template/customer-app-js/src/main/webapp/keycloak.json index 224c70b76f..4a60ffacd0 100644 --- a/examples/demo-template/customer-app-js/src/main/webapp/keycloak.json +++ b/examples/demo-template/customer-app-js/src/main/webapp/keycloak.json @@ -1,6 +1,5 @@ { "realm" : "demo", - "realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB", "auth-server-url" : "/auth", "ssl-required" : "external", "resource" : "customer-portal-js", diff --git a/examples/demo-template/customer-app/src/main/webapp/WEB-INF/keycloak.json b/examples/demo-template/customer-app/src/main/webapp/WEB-INF/keycloak.json index c2241b3e91..20619097b5 100755 --- a/examples/demo-template/customer-app/src/main/webapp/WEB-INF/keycloak.json +++ b/examples/demo-template/customer-app/src/main/webapp/WEB-INF/keycloak.json @@ -1,7 +1,6 @@ { "realm": "demo", "resource": "customer-portal", - "realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB", "auth-server-url": "/auth", "ssl-required" : "external", "expose-token": true, diff --git a/examples/demo-template/database-service/src/main/webapp/WEB-INF/keycloak.json b/examples/demo-template/database-service/src/main/webapp/WEB-INF/keycloak.json index cb938548dc..72e4903bc6 100755 --- a/examples/demo-template/database-service/src/main/webapp/WEB-INF/keycloak.json +++ b/examples/demo-template/database-service/src/main/webapp/WEB-INF/keycloak.json @@ -1,7 +1,6 @@ { "realm" : "demo", "resource" : "database-service", - "realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB", "auth-server-url": "/auth", "bearer-only" : true, "ssl-required" : "external" diff --git a/examples/demo-template/offline-access-app/src/main/webapp/WEB-INF/keycloak.json b/examples/demo-template/offline-access-app/src/main/webapp/WEB-INF/keycloak.json index dff976cf0e..600dcfa8a8 100644 --- a/examples/demo-template/offline-access-app/src/main/webapp/WEB-INF/keycloak.json +++ b/examples/demo-template/offline-access-app/src/main/webapp/WEB-INF/keycloak.json @@ -1,7 +1,6 @@ { "realm": "demo", "resource": "offline-access-portal", - "realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB", "auth-server-url": "/auth", "ssl-required" : "external", "credentials": { diff --git a/examples/demo-template/pom.xml b/examples/demo-template/pom.xml index b7fc0274ee..dbc0218250 100755 --- a/examples/demo-template/pom.xml +++ b/examples/demo-template/pom.xml @@ -51,6 +51,7 @@ example-ear admin-access-app angular-product-app + angular2-product-app database-service third-party third-party-cdi diff --git a/examples/demo-template/product-app/src/main/webapp/WEB-INF/keycloak.json b/examples/demo-template/product-app/src/main/webapp/WEB-INF/keycloak.json index 0a86c041c7..109270101f 100755 --- a/examples/demo-template/product-app/src/main/webapp/WEB-INF/keycloak.json +++ b/examples/demo-template/product-app/src/main/webapp/WEB-INF/keycloak.json @@ -1,7 +1,6 @@ { "realm" : "demo", "resource" : "product-portal", - "realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB", "auth-server-url" : "/auth", "ssl-required" : "external", "credentials" : { diff --git a/examples/demo-template/service-account/src/main/java/org/keycloak/example/ProductServiceAccountServlet.java b/examples/demo-template/service-account/src/main/java/org/keycloak/example/ProductServiceAccountServlet.java index e4d6a4033a..72ffd4959b 100644 --- a/examples/demo-template/service-account/src/main/java/org/keycloak/example/ProductServiceAccountServlet.java +++ b/examples/demo-template/service-account/src/main/java/org/keycloak/example/ProductServiceAccountServlet.java @@ -41,6 +41,7 @@ import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.message.BasicNameValuePair; import org.keycloak.OAuth2Constants; import org.keycloak.RSATokenVerifier; +import org.keycloak.adapters.rotation.AdapterRSATokenVerifier; import org.keycloak.common.VerificationException; import org.keycloak.adapters.KeycloakDeployment; import org.keycloak.adapters.KeycloakDeploymentBuilder; @@ -163,7 +164,7 @@ public abstract class ProductServiceAccountServlet extends HttpServlet { 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()); + AccessToken tokenParsed = AdapterRSATokenVerifier.verifyToken(token, deployment); req.getSession().setAttribute(TOKEN, token); req.getSession().setAttribute(REFRESH_TOKEN, refreshToken); req.getSession().setAttribute(TOKEN_PARSED, tokenParsed); diff --git a/examples/demo-template/service-account/src/main/webapp/WEB-INF/keycloak-client-secret.json b/examples/demo-template/service-account/src/main/webapp/WEB-INF/keycloak-client-secret.json index 7eec22a6c3..1a9322d96a 100644 --- a/examples/demo-template/service-account/src/main/webapp/WEB-INF/keycloak-client-secret.json +++ b/examples/demo-template/service-account/src/main/webapp/WEB-INF/keycloak-client-secret.json @@ -1,6 +1,5 @@ { "realm" : "demo", - "realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB", "auth-server-url" : "http://localhost:8080/auth", "ssl-required" : "external", "resource" : "product-sa-client", diff --git a/examples/demo-template/service-account/src/main/webapp/WEB-INF/keycloak-client-signed-jwt.json b/examples/demo-template/service-account/src/main/webapp/WEB-INF/keycloak-client-signed-jwt.json index 3e90c34a30..3c99da7b42 100644 --- a/examples/demo-template/service-account/src/main/webapp/WEB-INF/keycloak-client-signed-jwt.json +++ b/examples/demo-template/service-account/src/main/webapp/WEB-INF/keycloak-client-signed-jwt.json @@ -1,6 +1,5 @@ { "realm" : "demo", - "realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB", "auth-server-url" : "http://localhost:8080/auth", "ssl-required" : "external", "resource" : "product-sa-client-jwt-auth", diff --git a/examples/fuse/camel/src/main/resources/OSGI-INF/blueprint/blueprint.xml b/examples/fuse/camel/src/main/resources/OSGI-INF/blueprint/blueprint.xml index a4796cc152..56550d681a 100644 --- a/examples/fuse/camel/src/main/resources/OSGI-INF/blueprint/blueprint.xml +++ b/examples/fuse/camel/src/main/resources/OSGI-INF/blueprint/blueprint.xml @@ -26,7 +26,6 @@ - diff --git a/examples/fuse/customer-app-fuse/src/main/webapp/WEB-INF/keycloak.json b/examples/fuse/customer-app-fuse/src/main/webapp/WEB-INF/keycloak.json index b5d6b30d24..c7f61f68e3 100755 --- a/examples/fuse/customer-app-fuse/src/main/webapp/WEB-INF/keycloak.json +++ b/examples/fuse/customer-app-fuse/src/main/webapp/WEB-INF/keycloak.json @@ -1,7 +1,6 @@ { "realm": "demo", "resource": "customer-portal", - "realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB", "auth-server-url": "http://localhost:8080/auth", "ssl-required" : "external", "credentials": { diff --git a/examples/fuse/cxf-jaxrs/src/main/resources/WEB-INF/keycloak.json b/examples/fuse/cxf-jaxrs/src/main/resources/WEB-INF/keycloak.json index f5d7e1a989..a8c4b752e1 100644 --- a/examples/fuse/cxf-jaxrs/src/main/resources/WEB-INF/keycloak.json +++ b/examples/fuse/cxf-jaxrs/src/main/resources/WEB-INF/keycloak.json @@ -1,7 +1,6 @@ { "realm": "demo", "resource": "builtin-cxf-app", - "realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB", "auth-server-url": "http://localhost:8080/auth", "ssl-required" : "external", "credentials": { diff --git a/examples/fuse/cxf-jaxws/src/main/resources/META-INF/spring/beans.xml b/examples/fuse/cxf-jaxws/src/main/resources/META-INF/spring/beans.xml index 2d15ae017b..8a808c623c 100644 --- a/examples/fuse/cxf-jaxws/src/main/resources/META-INF/spring/beans.xml +++ b/examples/fuse/cxf-jaxws/src/main/resources/META-INF/spring/beans.xml @@ -32,7 +32,6 @@ - diff --git a/examples/fuse/external-config/external-config-keycloak.json b/examples/fuse/external-config/external-config-keycloak.json index 920e99a677..469da82a17 100644 --- a/examples/fuse/external-config/external-config-keycloak.json +++ b/examples/fuse/external-config/external-config-keycloak.json @@ -1,7 +1,6 @@ { "realm": "demo", "resource": "external-config", - "realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB", "auth-server-url": "http://localhost:8080/auth", "ssl-required" : "external", "credentials": { diff --git a/examples/fuse/fuse-admin/keycloak-direct-access.json b/examples/fuse/fuse-admin/keycloak-direct-access.json index 8e5ac0ff24..2441134231 100644 --- a/examples/fuse/fuse-admin/keycloak-direct-access.json +++ b/examples/fuse/fuse-admin/keycloak-direct-access.json @@ -1,7 +1,6 @@ { "realm": "demo", "resource": "ssh-jmx-admin-client", - "realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB", "ssl-required" : "external", "auth-server-url" : "http://localhost:8080/auth", "credentials": { diff --git a/examples/fuse/product-app-fuse/src/main/resources/WEB-INF/keycloak.json b/examples/fuse/product-app-fuse/src/main/resources/WEB-INF/keycloak.json index 2a52d247f7..e90433ac51 100644 --- a/examples/fuse/product-app-fuse/src/main/resources/WEB-INF/keycloak.json +++ b/examples/fuse/product-app-fuse/src/main/resources/WEB-INF/keycloak.json @@ -1,7 +1,6 @@ { "realm": "demo", "resource": "product-portal", - "realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB", "auth-server-url": "http://localhost:8080/auth", "ssl-required" : "external", "credentials": { diff --git a/examples/js-console/src/main/webapp/keycloak.json b/examples/js-console/src/main/webapp/keycloak.json index c0c04d5aee..cc4bab3394 100644 --- a/examples/js-console/src/main/webapp/keycloak.json +++ b/examples/js-console/src/main/webapp/keycloak.json @@ -1,6 +1,5 @@ { "realm" : "example", - "realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB", "auth-server-url" : "/auth", "ssl-required" : "external", "resource" : "js-console", diff --git a/examples/kerberos/src/main/webapp/WEB-INF/keycloak.json b/examples/kerberos/src/main/webapp/WEB-INF/keycloak.json index db1223c6b2..7e9d91a7da 100644 --- a/examples/kerberos/src/main/webapp/WEB-INF/keycloak.json +++ b/examples/kerberos/src/main/webapp/WEB-INF/keycloak.json @@ -1,7 +1,6 @@ { "realm" : "kerberos-demo", "resource" : "kerberos-app", - "realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB", "auth-server-url": "/auth", "ssl-required" : "external", "credentials": { diff --git a/examples/ldap/src/main/webapp/WEB-INF/keycloak.json b/examples/ldap/src/main/webapp/WEB-INF/keycloak.json index 84e1129a88..f43107b054 100644 --- a/examples/ldap/src/main/webapp/WEB-INF/keycloak.json +++ b/examples/ldap/src/main/webapp/WEB-INF/keycloak.json @@ -1,7 +1,6 @@ { "realm" : "ldap-demo", "resource" : "ldap-app", - "realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB", "auth-server-url": "/auth", "ssl-required" : "external", "credentials": { diff --git a/examples/multi-tenant/src/main/resources/tenant1-keycloak.json b/examples/multi-tenant/src/main/resources/tenant1-keycloak.json index 57be2774e7..34c17737b3 100644 --- a/examples/multi-tenant/src/main/resources/tenant1-keycloak.json +++ b/examples/multi-tenant/src/main/resources/tenant1-keycloak.json @@ -1,7 +1,6 @@ { "realm" : "tenant1", "resource" : "multi-tenant", - "realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB", "auth-server-url" : "http://localhost:8080/auth", "ssl-required" : "external", "credentials" : { diff --git a/examples/multi-tenant/src/main/resources/tenant2-keycloak.json b/examples/multi-tenant/src/main/resources/tenant2-keycloak.json index 4f221dc66d..5877082e35 100644 --- a/examples/multi-tenant/src/main/resources/tenant2-keycloak.json +++ b/examples/multi-tenant/src/main/resources/tenant2-keycloak.json @@ -1,7 +1,6 @@ { "realm" : "tenant2", "resource" : "multi-tenant", - "realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDA0oJjgPQJhnVhOo51KauQGfLLreMFu64OJdKXRnfvAQJQTuKNwc5JrR63l/byyW1B6FgclABF818TtLvMCAkn4EuFwQZCZhg3x3+lFGiB/IzC6UAt4Bi0JQrTbdh83/U97GIPegvaDqiqEiQESEkbCZWxM6sh/34hQaAhCaFpMwIDAQAB", "auth-server-url" : "http://localhost:8080/auth", "ssl-required" : "external", "credentials" : { diff --git a/examples/providers/authenticator/README.md b/examples/providers/authenticator/README.md index 1edf0ed693..54dc752734 100755 --- a/examples/providers/authenticator/README.md +++ b/examples/providers/authenticator/README.md @@ -6,13 +6,12 @@ of Keycloak. To deploy, build this directory then take the jar and copy it to KEYCLOAK_HOME/bin/jboss-cli.sh --command="module add --name=org.keycloak.examples.secret-question --resources=target/authenticator-required-action-example.jar --dependencies=org.keycloak.keycloak-core,org.keycloak.keycloak-server-spi,org.keycloak.keycloak-services,org.jboss.resteasy.resteasy-jaxrs,javax.ws.rs.api" -Then registering the provider by editing keycloak-server.json and adding the module to the providers field: - - "providers": [ - .... - "module:org.keycloak.examples.secret-question" - ], +Then registering the provider by editing `standalone/configuration/standalone.xml` and adding the module to the providers element: + + ... + module:org.keycloak.examples.secret-question + You then have to copy the secret-question.ftl and secret-question-config.ftl files to the themes/base/login directory. diff --git a/examples/providers/domain-extension/README.md b/examples/providers/domain-extension/README.md index e1aa2cdb62..51e2b3c56a 100644 --- a/examples/providers/domain-extension/README.md +++ b/examples/providers/domain-extension/README.md @@ -6,12 +6,12 @@ To run, deploy as a module by running: $KEYCLOAK_HOME/bin/jboss-cli.sh --command="module add --name=org.keycloak.examples.domain-extension-example --resources=target/domain-extension-example.jar --dependencies=org.keycloak.keycloak-core,org.keycloak.keycloak-services,org.keycloak.keycloak-model-jpa,org.keycloak.keycloak-server-spi,javax.ws.rs.api,javax.persistence.api,org.hibernate,org.javassist" -Then registering the provider by editing keycloak-server.json and adding the module to the providers field: +Then registering the provider by editing `standalone/configuration/standalone.xml` and adding the module to the providers element: - "providers": [ - .... - "module:org.keycloak.examples.domain-extension-example" - ], + + ... + module:org.keycloak.examples.domain-extension-example + Then start (or restart) the server. diff --git a/examples/providers/event-listener-sysout/README.md b/examples/providers/event-listener-sysout/README.md index 57519f3e2c..8e1a0850ad 100644 --- a/examples/providers/event-listener-sysout/README.md +++ b/examples/providers/event-listener-sysout/README.md @@ -5,23 +5,25 @@ To deploy copy target/event-listener-sysout-example.jar to providers directory. KEYCLOAK_HOME/bin/jboss-cli.sh --command="module add --name=org.keycloak.examples.event-sysout --resources=target/event-listener-sysout-example.jar --dependencies=org.keycloak.keycloak-core,org.keycloak.keycloak-server-spi" -Then registering the provider by editing keycloak-server.json and adding the module to the providers field: +Then registering the provider by editing `standalone/configuration/standalone.xml` and adding the module to the providers element: - "providers": [ - .... - "module:org.keycloak.examples.event-sysout" - ], + + ... + module:org.keycloak.examples.event-sysout + Then start (or restart) the server. Once started open the admin console, select your realm, then click on Events, followed by config. Click on Listeners select box, then pick sysout from the dropdown. After this try to logout and login again to see events printed to System.out. The example event listener can be configured to exclude certain events, for example to exclude REFRESH_TOKEN and -CODE_TO_TOKEN events add the following to keycloak-server.json: +CODE_TO_TOKEN events add the following to `standalone.xml`: ... - "eventsListener": { - "sysout": { - "exclude": [ "REFRESH_TOKEN", "CODE_TO_TOKEN" ] - } - } + + + + + + diff --git a/examples/providers/event-store-mem/README.md b/examples/providers/event-store-mem/README.md index d533fdad69..682ff42b78 100644 --- a/examples/providers/event-store-mem/README.md +++ b/examples/providers/event-store-mem/README.md @@ -5,24 +5,24 @@ To deploy copy target/event-store-mem-example.jar to providers directory. Altern KEYCLOAK_HOME/bin/jboss-cli.sh --command="module add --name=org.keycloak.examples.event-inmem --resources=target/event-store-mem-example.jar --dependencies=org.keycloak.keycloak-core,org.keycloak.keycloak-server-spi" -Then registering the provider by editing keycloak-server.json and adding the module to the providers field: +Then registering the provider by editing `standalone/configuration/standalone.xml` and adding the module to the providers element: - "providers": [ - .... - "module:org.keycloak.examples.event-inmem" - ], + + ... + module:org.keycloak.examples.event-inmem + -Then edit standalone/configuration/keycloak-server.json, change: +Then edit `standalone/configuration/standalone.xml`, change: - "eventsStore": { - "provider": "jpa" - } + + jpa + to: - "eventsStore": { - "provider": "in-mem" - } + + in-mem + Then start (or restart)the server. Once started open the admin console, select your realm, then click on Events, followed by config. Set the toggle for Enabled to ON. After this try to logout and login again then open the Events tab diff --git a/examples/providers/federation-provider/README.md b/examples/providers/federation-provider/README.md index c8b443079d..c90e791547 100755 --- a/examples/providers/federation-provider/README.md +++ b/examples/providers/federation-provider/README.md @@ -6,13 +6,12 @@ key pairs. To deploy, build this directory then take the jar and copy it to pro KEYCLOAK_HOME/bin/jboss-cli.sh --command="module add --name=org.keycloak.examples.userprops --resources=target/federation-properties-example.jar --dependencies=org.keycloak.keycloak-core,org.keycloak.keycloak-server-spi" -Then registering the provider by editing keycloak-server.json and adding the module to the providers field: - - "providers": [ - .... - "module:org.keycloak.examples.userprops" - ], +Then registering the provider by editing `standalone/configuration/standalone.xml` and adding the module to the providers element: + + ... + module:org.keycloak.examples.userprops + You will then have to restart the authentication server. diff --git a/examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/BasePropertiesFederationFactory.java b/examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/BasePropertiesFederationFactory.java index 2d5abc3f22..e9ec451af5 100755 --- a/examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/BasePropertiesFederationFactory.java +++ b/examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/BasePropertiesFederationFactory.java @@ -96,7 +96,7 @@ public abstract class BasePropertiesFederationFactory implements UserFederationP } /** - * You can import additional plugin configuration from keycloak-server.json here. + * You can import additional plugin configuration from standalone.xml here. * * @param config */ diff --git a/examples/providers/rest/README.md b/examples/providers/rest/README.md index 5124f88b1d..5ee9327132 100644 --- a/examples/providers/rest/README.md +++ b/examples/providers/rest/README.md @@ -5,12 +5,12 @@ To deploy copy target/hello-rest-example.jar to providers directory. Alternative $KEYCLOAK_HOME/bin/jboss-cli.sh --command="module add --name=org.keycloak.examples.hello-rest-example --resources=target/hello-rest-example.jar --dependencies=org.keycloak.keycloak-core,org.keycloak.keycloak-server-spi,javax.ws.rs.api" -Then registering the provider by editing keycloak-server.json and adding the module to the providers field: +Then registering the provider by editing `standalone/configuration/standalone.xml` and adding the module to the providers element: - "providers": [ - .... - "module:org.keycloak.examples.hello-rest-example" - ], + + ... + module:org.keycloak.examples.hello-rest-example + Then start (or restart) the server. Once started open http://localhost:8080/realms/master/hello and you should see the message _Hello master_. You can also invoke the endpoint for other realms by replacing `master` with the realm name in the above url. \ No newline at end of file diff --git a/examples/themes/README.md b/examples/themes/README.md index ea160bc650..5089ca1e7f 100644 --- a/examples/themes/README.md +++ b/examples/themes/README.md @@ -17,14 +17,14 @@ Alternatively you can deploy as modules. This can be done by first running: mvn clean install $KEYCLOAK_HOME/bin/jboss-cli.sh --command="module add --name=org.keycloak.example.themes --resources=target/keycloak-example-themes.jar" -Then open $KEYCLOAK_HOME/standalone/configuration/keycloak-server.json and register the theme module by adding: - - "theme": { - "module": { - "modules": [ "org.keycloak.example.themes" ] - } - } +Then open `standalone/configuration/standalone.xml` and register the theme module by adding: + + ... + + org.keycloak.example.themes + + Address Theme ------------------- @@ -45,11 +45,11 @@ Change Logo Theme To enable the theme open the admin console, select your realm, click on `Theme`. In the dropdowns for `Login Theme`, `Account Theme` and `Admin Console Theme` select `logo-example`. Click `Save` and login to the realm to see the new theme in action. -To change the theme for the welcome pages open `standalone/configuration/keycloak-server.json` find the config for `theme` and add 'welcomeTheme': +To change the theme for the welcome pages open `standalone/configuration/standalone.xml` find the config for `theme` and add 'welcomeTheme': - "theme": { + ... - "welcomeTheme": "logo-example" - }, + logo-example + One thing to note is that to change the admin console for the master admin console (`/auth/admin`) you need to change the theme for the master realm. Changing the admin console theme for any other realms will only change the admin console for that specific realm (for example `/auth/admin/myrealm/console`). diff --git a/federation/ldap/pom.xml b/federation/ldap/pom.xml index f6a8c4bae1..257c617af0 100755 --- a/federation/ldap/pom.xml +++ b/federation/ldap/pom.xml @@ -74,6 +74,11 @@ junit test
+ + org.jboss.spec.javax.transaction + jboss-transaction-api_1.2_spec + provided + diff --git a/federation/pom.xml b/federation/pom.xml index 663f857a0b..dc702ccfab 100755 --- a/federation/pom.xml +++ b/federation/pom.xml @@ -35,6 +35,7 @@ ldap kerberos + sssd diff --git a/federation/sssd/pom.xml b/federation/sssd/pom.xml new file mode 100644 index 0000000000..d3e3afbb65 --- /dev/null +++ b/federation/sssd/pom.xml @@ -0,0 +1,70 @@ + + + + keycloak-parent + org.keycloak + 2.2.0-SNAPSHOT + ../../pom.xml + + 4.0.0 + + keycloak-sssd-federation + Keycloak SSSD Federation + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + ${maven.compiler.source} + ${maven.compiler.target} + + + + org.apache.maven.plugins + maven-shade-plugin + 2.3 + + + + package + + shade + + + + + + + + + + junit + junit + test + + + net.java.dev.jna + jna + + + org.keycloak + keycloak-core + provided + + + org.keycloak + keycloak-server-spi + provided + + + org.jboss.logging + jboss-logging + provided + + + + diff --git a/federation/sssd/src/main/java/cx/ath/matthew/LibraryLoader.java b/federation/sssd/src/main/java/cx/ath/matthew/LibraryLoader.java new file mode 100644 index 0000000000..4088d46ebb --- /dev/null +++ b/federation/sssd/src/main/java/cx/ath/matthew/LibraryLoader.java @@ -0,0 +1,46 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package cx.ath.matthew; + +/** + * @author Bruno Oliveira. + */ +public class LibraryLoader { + + private static final String[] PATHS = {"/usr/lib/", "/usr/lib64/", "/usr/local/lib/", "/opt/local/lib/"}; + private static final String LIBRARY_NAME = "libunix_dbus_java"; + private static final String VERSION = "0.0.8"; + private static boolean loadSucceeded; + + public static LibraryLoader load() { + for (String path : PATHS) { + try { + System.load(String.format("%s/%s.so.%s", path, LIBRARY_NAME, VERSION)); + loadSucceeded = true; + break; + } catch (UnsatisfiedLinkError e) { + loadSucceeded = false; + } + } + + return new LibraryLoader(); + } + + public boolean succeed() { + return loadSucceeded; + } +} diff --git a/federation/sssd/src/main/java/cx/ath/matthew/debug/Debug.java b/federation/sssd/src/main/java/cx/ath/matthew/debug/Debug.java new file mode 100644 index 0000000000..30c4d8549d --- /dev/null +++ b/federation/sssd/src/main/java/cx/ath/matthew/debug/Debug.java @@ -0,0 +1,671 @@ +/* Copyright (C) 1991-2015 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + . */ +/* This header is separate from features.h so that the compiler can + include it implicitly at the start of every compilation. It must + not itself include or any other header that includes + because the implicit include comes before any feature + test macros that may be defined in a source file before it first + explicitly includes a system header. GCC knows the name of this + header in order to preinclude it. */ +/* glibc's intent is to support the IEC 559 math functionality, real + and complex. If the GCC (4.9 and later) predefined macros + specifying compiler intent are available, use them to determine + whether the overall intent is to support these features; otherwise, + presume an older compiler has intent to support these features and + define these macros by default. */ +/* wchar_t uses Unicode 7.0.0. Version 7.0 of the Unicode Standard is + synchronized with ISO/IEC 10646:2012, plus Amendments 1 (published + on April, 2013) and 2 (not yet published as of February, 2015). + Additionally, it includes the accelerated publication of U+20BD + RUBLE SIGN. Therefore Unicode 7.0.0 is between 10646:2012 and + 10646:2014, and so we use the date ISO/IEC 10646:2012 Amd.1 was + published. */ +/* We do not support C11 . */ +/* + * Java Debug Library + * + * Copyright (c) Matthew Johnson 2005 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * To Contact the author, please email src@matthew.ath.cx + * + */ +package cx.ath.matthew.debug; + +import cx.ath.matthew.utils.Hexdump; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Properties; + +/** + * Add debugging to your program, has support for large projects with multiple + * classes and debug levels per class. Supports optional enabling of debug + * per-level per-class and debug targets of files, Streams or stderr. + * Also supports timing between debug outputs, printing of stack traces for Throwables + * and files/line numbers on each message. + *

+ * Debug now automatically figures out which class it was called from, so all + * methods passing in the calling class are deprecated. + *

+ *

+ * The defaults are to print all messages to stderr with class and method name. + *

+ *

+ * Should be called like this: + *

+ *
+ * if (Debug.debug) Debug.print(Debug.INFO, "Debug Message");
+ * 
+ */ +public class Debug { + /** + * This interface can be used to provide custom printing filters + * for certain classes. + */ + public static interface FilterCommand { + /** + * Called to print debug messages with a custom filter. + * + * @param output The PrintStream to output to. + * @param level The debug level of this message. + * @param location The textual location of the message. + * @param extra Extra information such as timing details. + * @param message The debug message. + * @param lines Other lines of a multiple-line debug message. + */ + public void filter(PrintStream output, int level, String location, String extra, String message, String[] lines); + } + + /** + * Highest priority messages + */ + public static final int CRIT = 1; + /** + * Error messages + */ + public static final int ERR = 2; + /** + * Warnings + */ + public static final int WARN = 3; + /** + * Information + */ + public static final int INFO = 4; + /** + * Debug messages + */ + public static final int DEBUG = 5; + /** + * Verbose debug messages + */ + public static final int VERBOSE = 6; + /** + * Set this to false to disable compilation of Debug statements + */ + public static final boolean debug = false; + /** + * The current output stream (defaults to System.err) + */ + public static PrintStream debugout = System.err; + private static Properties prop = null; + private static boolean timing = false; + private static boolean ttrace = false; + private static boolean lines = false; + private static boolean hexdump = false; + private static long last = 0; + private static int balen = 36; + private static int bawidth = 80; + private static Class saveclass = null; + //TODO: 1.5 private static Map, FilterCommand> filterMap = new HashMap, FilterCommand>(); + private static Map filterMap = new HashMap(); + + /** + * Set properties to configure debugging. + * Format of properties is class => level, e.g. + *
+     * cx.ath.matthew.io.TeeOutputStream = INFO
+     * cx.ath.matthew.io.DOMPrinter = DEBUG
+     * 
+ * The debug level can be one of CRIT, ERR, WARN, INFO, DEBUG or VERBOSE which + * correspond to all messages up to that level. The special words YES, ALL and TRUE + * cause all messages to be printed regardless of level. All other terms disable + * messages for that class. CRIT and ERR messages are always printed if debugging is enabled + * unless explicitly disabled. + * The special class name ALL can be used to set the default level for all classes. + * + * @param prop Properties object to use. + */ + public static void setProperties(Properties prop) { + Debug.prop = prop; + } + + /** + * Read which class to debug on at which level from the given File. + * Syntax the same as Java Properties files: + *
+     * <class> = <debuglevel>
+     * 
+ * E.G. + *
+     * cx.ath.matthew.io.TeeOutputStream = INFO
+     * cx.ath.matthew.io.DOMPrinter = DEBUG
+     * 
+ * The debug level can be one of CRIT, ERR, WARN, INFO, DEBUG or VERBOSE which + * correspond to all messages up to that level. The special words YES, ALL and TRUE + * cause all messages to be printed regardless of level. All other terms disable + * messages for that class. CRIT and ERR messages are always printed if debugging is enabled + * unless explicitly disabled. + * The special class name ALL can be used to set the default level for all classes. + * + * @param f File to read from. + */ + public static void loadConfig(File f) throws IOException { + prop = new Properties(); + prop.load(new FileInputStream(f)); + } + + /** + * @deprecated In Java 1.5 calling class is automatically identified, no need to pass it in. + */ + //TODO: 1.5 @Deprecated() + public static boolean debugging(Class c, int loglevel) { + if (debug) { + if (null == c) return true; + return debugging(c.getName(), loglevel); + } + return false; + } + + public static boolean debugging(String s, int loglevel) { + if (debug) { + try { + if (null == s) return true; + if (null == prop) return loglevel <= DEBUG; + String d = prop.getProperty(s); + if (null == d || "".equals(d)) d = prop.getProperty("ALL"); + if (null == d) return loglevel <= ERR; + if ("".equals(d)) return loglevel <= ERR; + d = d.toLowerCase(); + if ("true".equals(d)) return true; + if ("yes".equals(d)) return true; + if ("all".equals(d)) return true; + if ("verbose".equals(d)) return loglevel <= VERBOSE; + if ("debug".equals(d)) return loglevel <= DEBUG; + if ("info".equals(d)) return loglevel <= INFO; + if ("warn".equals(d)) return loglevel <= WARN; + if ("err".equals(d)) return loglevel <= ERR; + if ("crit".equals(d)) return loglevel <= CRIT; + int i = Integer.parseInt(d); + return i >= loglevel; + } catch (Exception e) { + return false; + } + } + return false; + } + + /** + * Output to the given Stream + */ + public static void setOutput(PrintStream p) throws IOException { + debugout = p; + } + + /** + * Output to the given file + */ + public static void setOutput(String filename) throws IOException { + debugout = new PrintStream(new FileOutputStream(filename, true)); + } + + /** + * Output to the default debug.log + */ + public static void setOutput() throws IOException { + setOutput("./debug.log"); + } + + /** + * Log at DEBUG + * + * @param d The object to log + */ + public static void print(Object d) { + if (debug) { + if (d instanceof String) + print(DEBUG, (String) d); + else if (d instanceof Throwable) + print(DEBUG, (Throwable) d); + else if (d instanceof byte[]) + print(DEBUG, (byte[]) d); + else if (d instanceof Map) + printMap(DEBUG, (Map) d); + else print(DEBUG, d); + } + } + + /** + * Log at DEBUG + * + * @param o The object doing the logging + * @param d The object to log + * @deprecated In Java 1.5 calling class is automatically identified, no need to pass it in. + */ + //TODO: 1.5 @Deprecated() + public static void print(Object o, Object d) { + if (debug) { + if (o instanceof Class) + saveclass = (Class) o; + else + saveclass = o.getClass(); + print(d); + } + } + + /** + * Log an Object + * + * @param o The object doing the logging + * @param loglevel The level to log at (DEBUG, WARN, etc) + * @param d The object to log with d.toString() + * @deprecated In Java 1.5 calling class is automatically identified, no need to pass it in. + */ + //TODO: 1.5 @Deprecated() + public static void print(Object o, int loglevel, Object d) { + if (debug) { + if (o instanceof Class) + saveclass = (Class) o; + else + saveclass = o.getClass(); + print(loglevel, d); + } + } + + /** + * Log a String + * + * @param o The object doing the logging + * @param loglevel The level to log at (DEBUG, WARN, etc) + * @param s The log message + * @deprecated In Java 1.5 calling class is automatically identified, no need to pass it in. + */ + //TODO: 1.5 @Deprecated() + public static void print(Object o, int loglevel, String s) { + if (debug) { + if (o instanceof Class) + saveclass = (Class) o; + else + saveclass = o.getClass(); + print(loglevel, s); + } + } + + /** + * Log a Throwable + * + * @param o The object doing the logging + * @param loglevel The level to log at (DEBUG, WARN, etc) + * @param t The throwable to log with .toString and .printStackTrace + * @deprecated In Java 1.5 calling class is automatically identified, no need to pass it in. + */ + //TODO: 1.5 @Deprecated() + public static void print(Object o, int loglevel, Throwable t) { + if (debug) { + if (o instanceof Class) + saveclass = (Class) o; + else + saveclass = o.getClass(); + print(loglevel, t); + } + } + + /** + * Log a Throwable + * + * @param c The class doing the logging + * @param loglevel The level to log at (DEBUG, WARN, etc) + * @param t The throwable to log with .toString and .printStackTrace + * @deprecated In Java 1.5 calling class is automatically identified, no need to pass it in. + */ + //TODO: 1.5 @Deprecated() + public static void print(Class c, int loglevel, Throwable t) { + if (debug) { + saveclass = c; + print(loglevel, t); + } + } + + /** + * Log a Throwable + * + * @param loglevel The level to log at (DEBUG, WARN, etc) + * @param t The throwable to log with .toString and .printStackTrace + * @see #setThrowableTraces to turn on stack traces. + */ + public static void print(int loglevel, Throwable t) { + if (debug) { + String timestr = ""; + String[] data = getTraceElements(); + if (debugging(data[0], loglevel)) { + if (timing) { + long now = System.currentTimeMillis(); + timestr = "{" + (now - last) + "} "; + last = now; + } + String[] lines = null; + if (ttrace) { + StackTraceElement[] ste = t.getStackTrace(); + lines = new String[ste.length]; + for (int i = 0; i < ste.length; i++) + lines[i] = "\tat " + ste[i].toString(); + } + _print(t.getClass(), loglevel, data[0] + "." + data[1] + "()" + data[2], timestr, t.toString(), lines); + } + } + } + + /** + * Log a byte array + * + * @param loglevel The level to log at (DEBUG, WARN, etc) + * @param b The byte array to print. + * @see #setHexDump to enable hex dumping. + * @see #setByteArrayCount to change how many bytes are printed. + * @see #setByteArrayWidth to change the formatting width of hex. + */ + public static void print(int loglevel, byte[] b) { + if (debug) { + String timestr = ""; + String[] data = getTraceElements(); + if (debugging(data[0], loglevel)) { + if (timing) { + long now = System.currentTimeMillis(); + timestr = "{" + (now - last) + "} "; + last = now; + } + String[] lines = null; + if (hexdump) { + if (balen >= b.length) + lines = Hexdump.format(b, bawidth).split("\n"); + else { + byte[] buf = new byte[balen]; + System.arraycopy(b, 0, buf, 0, balen); + lines = Hexdump.format(buf, bawidth).split("\n"); + } + } + _print(b.getClass(), loglevel, data[0] + "." + data[1] + "()" + data[2], timestr, b.length + " bytes", lines); + } + } + } + + /** + * Log a String + * + * @param loglevel The level to log at (DEBUG, WARN, etc) + * @param s The string to log with d.toString() + */ + public static void print(int loglevel, String s) { + if (debug) + print(loglevel, (Object) s); + } + + /** + * Log an Object + * + * @param c The class doing the logging + * @param loglevel The level to log at (DEBUG, WARN, etc) + * @param d The object to log with d.toString() + * @deprecated In Java 1.5 calling class is automatically identified, no need to pass it in. + */ + //TODO: 1.5 @Deprecated() + public static void print(Class c, int loglevel, Object d) { + if (debug) { + saveclass = c; + print(loglevel, d); + } + } + + /** + * Log a String + * + * @param c The class doing the logging + * @param loglevel The level to log at (DEBUG, WARN, etc) + * @param s The log message + * @deprecated In Java 1.5 calling class is automatically identified, no need to pass it in. + */ + //TODO: 1.5 @Deprecated() + public static void print(Class c, int loglevel, String s) { + if (debug) { + saveclass = c; + print(loglevel, s); + } + } + + private static String[] getTraceElements() { + String[] data = new String[]{"", "", ""}; + try { + Method m = Thread.class.getMethod("getStackTrace", new Class[0]); + StackTraceElement[] stes = (StackTraceElement[]) m.invoke(Thread.currentThread(), new Object[0]); + for (StackTraceElement ste : stes) { + if (Debug.class.getName().equals(ste.getClassName())) continue; + if (Thread.class.getName().equals(ste.getClassName())) continue; + if (Method.class.getName().equals(ste.getClassName())) continue; + if (ste.getClassName().startsWith("sun.reflect")) continue; + data[0] = ste.getClassName(); + data[1] = ste.getMethodName(); + if (lines) + data[2] = " " + ste.getFileName() + ":" + ste.getLineNumber(); + break; + } + } catch (NoSuchMethodException NSMe) { + if (null != saveclass) + data[0] = saveclass.getName(); + } catch (IllegalAccessException IAe) { + } catch (InvocationTargetException ITe) { + } + return data; + } + + /** + * Log an Object + * + * @param loglevel The level to log at (DEBUG, WARN, etc) + * @param o The object to log + */ + public static void print(int loglevel, Object o) { + if (debug) { + String timestr = ""; + String[] data = getTraceElements(); + if (debugging(data[0], loglevel)) { + if (timing) { + long now = System.currentTimeMillis(); + timestr = "{" + (now - last) + "} "; + last = now; + } + _print(o.getClass(), loglevel, data[0] + "." + data[1] + "()" + data[2], timestr, o.toString(), null); + } + } + } + + /** + * Log a Map + * + * @param o The object doing the logging + * @param loglevel The level to log at (DEBUG, WARN, etc) + * @param m The Map to print out + * @deprecated In Java 1.5 calling class is automatically identified, no need to pass it in. + */ + //TODO: 1.5 @Deprecated() + public static void printMap(Object o, int loglevel, Map m) { + if (debug) { + if (o instanceof Class) + saveclass = (Class) o; + else + saveclass = o.getClass(); + printMap(loglevel, m); + } + } + + /** + * Log a Map + * + * @param c The class doing the logging + * @param loglevel The level to log at (DEBUG, WARN, etc) + * @param m The Map to print out + * @deprecated In Java 1.5 calling class is automatically identified, no need to pass it in. + */ + //TODO: 1.5 @Deprecated() + public static void printMap(Class c, int loglevel, Map m) { + if (debug) { + saveclass = c; + printMap(loglevel, m); + } + } + + /** + * Log a Map at DEBUG log level + * + * @param m The Map to print out + */ + public static void printMap(Map m) { + printMap(DEBUG, m); + } + + /** + * Log a Map + * + * @param loglevel The level to log at (DEBUG, WARN, etc) + * @param m The Map to print out + */ + public static void printMap(int loglevel, Map m) { + if (debug) { + String timestr = ""; + String[] data = getTraceElements(); + if (debugging(data[0], loglevel)) { + if (timing) { + long now = System.currentTimeMillis(); + timestr = "{" + (now - last) + "} "; + last = now; + } + Iterator i = m.keySet().iterator(); + String[] lines = new String[m.size()]; + int j = 0; + while (i.hasNext()) { + Object key = i.next(); + lines[j++] = "\t\t- " + key + " => " + m.get(key); + } + _print(m.getClass(), loglevel, data[0] + "." + data[1] + "()" + data[2], timestr, "Map:", lines); + } + } + } + + /** + * Enable or disable stack traces in Debuging throwables. + */ + public static void setThrowableTraces(boolean ttrace) { + Debug.ttrace = ttrace; + } + + /** + * Enable or disable timing in Debug messages. + */ + public static void setTiming(boolean timing) { + Debug.timing = timing; + } + + /** + * Enable or disable line numbers. + */ + public static void setLineNos(boolean lines) { + Debug.lines = lines; + } + + /** + * Enable or disable hexdumps. + */ + public static void setHexDump(boolean hexdump) { + Debug.hexdump = hexdump; + } + + /** + * Set the size of hexdumps. + * (Default: 36) + */ + public static void setByteArrayCount(int count) { + Debug.balen = count; + } + + /** + * Set the formatted width of hexdumps. + * (Default: 80 chars) + */ + public static void setByteArrayWidth(int width) { + Debug.bawidth = width; + } + + /** + * Add a filter command for a specific type. + * This command will be called with the output stream + * and the text to be sent. It should perform any + * changes necessary to the text and then print the + * result to the output stream. + */ + public static void addFilterCommand(Class c, FilterCommand f) + //TODO 1.5: public static void addFilterCommand(Class c, FilterCommand f) + { + filterMap.put(c, f); + } + + private static void _print(Class c, int level, String loc, String extra, String message, String[] lines) { + //TODO 1.5: FilterCommand f = filterMap.get(c); + FilterCommand f = (FilterCommand) filterMap.get(c); + if (null == f) { + debugout.println("[" + loc + "] " + extra + message); + if (null != lines) + for (String s : lines) + debugout.println(s); + } else + f.filter(debugout, level, loc, extra, message, lines); + } +} diff --git a/federation/sssd/src/main/java/cx/ath/matthew/unix/NotConnectedException.java b/federation/sssd/src/main/java/cx/ath/matthew/unix/NotConnectedException.java new file mode 100644 index 0000000000..836f5d6229 --- /dev/null +++ b/federation/sssd/src/main/java/cx/ath/matthew/unix/NotConnectedException.java @@ -0,0 +1,35 @@ +/* + * Java Unix Sockets Library + * + * Copyright (c) Matthew Johnson 2004 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * To Contact the author, please email src@matthew.ath.cx + * + */ +package cx.ath.matthew.unix; + +import java.net.SocketException; + +public class NotConnectedException extends SocketException { + public NotConnectedException() { + super("The Socket is Not Connected"); + } +} diff --git a/federation/sssd/src/main/java/cx/ath/matthew/unix/USInputStream.java b/federation/sssd/src/main/java/cx/ath/matthew/unix/USInputStream.java new file mode 100644 index 0000000000..eb143fe6a9 --- /dev/null +++ b/federation/sssd/src/main/java/cx/ath/matthew/unix/USInputStream.java @@ -0,0 +1,94 @@ +/* + * Java Unix Sockets Library + * + * Copyright (c) Matthew Johnson 2004 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * To Contact the author, please email src@matthew.ath.cx + * + */ +package cx.ath.matthew.unix; + +import java.io.IOException; +import java.io.InputStream; + +public class USInputStream extends InputStream { + public static final int MSG_DONTWAIT = 0x40; + + private native int native_recv(int sock, byte[] b, int off, int len, int flags, int timeout) throws IOException; + + private int sock; + boolean closed = false; + private byte[] onebuf = new byte[1]; + private UnixSocket us; + private boolean blocking = true; + private int flags = 0; + private int timeout = 0; + + public USInputStream(int sock, UnixSocket us) { + this.sock = sock; + this.us = us; + } + + public void close() throws IOException { + closed = true; + us.close(); + } + + public boolean markSupported() { + return false; + } + + public int read() throws IOException { + int rv = 0; + while (0 >= rv) rv = read(onebuf); + if (-1 == rv) return -1; + return 0 > onebuf[0] ? -onebuf[0] : onebuf[0]; + } + + public int read(byte[] b, int off, int len) throws IOException { + if (closed) throw new NotConnectedException(); + int count = native_recv(sock, b, off, len, flags, timeout); + /* Yes, I really want to do this. Recv returns 0 for 'connection shut down'. + * read() returns -1 for 'end of stream. + * Recv returns -1 for 'EAGAIN' (all other errors cause an exception to be raised) + * whereas read() returns 0 for '0 bytes read', so yes, I really want to swap them here. + */ + if (0 == count) return -1; + else if (-1 == count) return 0; + else return count; + } + + public boolean isClosed() { + return closed; + } + + public UnixSocket getSocket() { + return us; + } + + public void setBlocking(boolean enable) { + flags = enable ? 0 : MSG_DONTWAIT; + } + + public void setSoTimeout(int timeout) { + this.timeout = timeout; + } +} diff --git a/federation/sssd/src/main/java/cx/ath/matthew/unix/USOutputStream.java b/federation/sssd/src/main/java/cx/ath/matthew/unix/USOutputStream.java new file mode 100644 index 0000000000..d8c85a7718 --- /dev/null +++ b/federation/sssd/src/main/java/cx/ath/matthew/unix/USOutputStream.java @@ -0,0 +1,78 @@ +/* + * Java Unix Sockets Library + * + * Copyright (c) Matthew Johnson 2004 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * To Contact the author, please email src@matthew.ath.cx + * + */ +package cx.ath.matthew.unix; + +import java.io.IOException; +import java.io.OutputStream; + +public class USOutputStream extends OutputStream { + private native int native_send(int sock, byte[] b, int off, int len) throws IOException; + + private native int native_send(int sock, byte[][] b) throws IOException; + + private int sock; + boolean closed = false; + private byte[] onebuf = new byte[1]; + private UnixSocket us; + + public USOutputStream(int sock, UnixSocket us) { + this.sock = sock; + this.us = us; + } + + public void close() throws IOException { + closed = true; + us.close(); + } + + public void flush() { + } // no-op, we do not buffer + + public void write(byte[][] b) throws IOException { + if (closed) throw new NotConnectedException(); + native_send(sock, b); + } + + public void write(byte[] b, int off, int len) throws IOException { + if (closed) throw new NotConnectedException(); + native_send(sock, b, off, len); + } + + public void write(int b) throws IOException { + onebuf[0] = (byte) (b % 0x7F); + if (1 == (b % 0x80)) onebuf[0] = (byte) -onebuf[0]; + write(onebuf); + } + + public boolean isClosed() { + return closed; + } + + public UnixSocket getSocket() { + return us; + } +} diff --git a/federation/sssd/src/main/java/cx/ath/matthew/unix/UnixIOException.java b/federation/sssd/src/main/java/cx/ath/matthew/unix/UnixIOException.java new file mode 100644 index 0000000000..24fd20cd1f --- /dev/null +++ b/federation/sssd/src/main/java/cx/ath/matthew/unix/UnixIOException.java @@ -0,0 +1,43 @@ +/* + * Java Unix Sockets Library + * + * Copyright (c) Matthew Johnson 2004 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * To Contact the author, please email src@matthew.ath.cx + * + */ +package cx.ath.matthew.unix; + +import java.io.IOException; + +/** + * An IO Exception which occurred during UNIX Socket IO + */ +public class UnixIOException extends IOException { + private int no; + private String message; + + public UnixIOException(int no, String message) { + super(message); + this.message = message; + this.no = no; + } +} diff --git a/federation/sssd/src/main/java/cx/ath/matthew/unix/UnixSocket.java b/federation/sssd/src/main/java/cx/ath/matthew/unix/UnixSocket.java new file mode 100644 index 0000000000..8851637436 --- /dev/null +++ b/federation/sssd/src/main/java/cx/ath/matthew/unix/UnixSocket.java @@ -0,0 +1,350 @@ +/* + * Java Unix Sockets Library + * + * Copyright (c) Matthew Johnson 2004 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * To Contact the author, please email src@matthew.ath.cx + * + */ +package cx.ath.matthew.unix; + +import cx.ath.matthew.LibraryLoader; +import cx.ath.matthew.debug.Debug; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * Represents a UnixSocket. + */ +public class UnixSocket { + static { + LibraryLoader.load(); + } + + private native void native_set_pass_cred(int sock, boolean passcred) throws IOException; + + private native int native_connect(String address, boolean abs) throws IOException; + + private native void native_close(int sock) throws IOException; + + private native int native_getPID(int sock); + + private native int native_getUID(int sock); + + private native int native_getGID(int sock); + + private native void native_send_creds(int sock, byte data) throws IOException; + + private native byte native_recv_creds(int sock, int[] creds) throws IOException; + + private UnixSocketAddress address = null; + private USOutputStream os = null; + private USInputStream is = null; + private boolean closed = false; + private boolean connected = false; + private boolean passcred = false; + private int sock = 0; + private boolean blocking = true; + private int uid = -1; + private int pid = -1; + private int gid = -1; + + UnixSocket(int sock, UnixSocketAddress address) { + this.sock = sock; + this.address = address; + this.connected = true; + this.os = new USOutputStream(sock, this); + this.is = new USInputStream(sock, this); + } + + /** + * Create an unconnected socket. + */ + public UnixSocket() { + } + + /** + * Create a socket connected to the given address. + * + * @param address The Unix Socket address to connect to + */ + public UnixSocket(UnixSocketAddress address) throws IOException { + connect(address); + } + + /** + * Create a socket connected to the given address. + * + * @param address The Unix Socket address to connect to + */ + public UnixSocket(String address) throws IOException { + this(new UnixSocketAddress(address)); + } + + /** + * Connect the socket to this address. + * + * @param address The Unix Socket address to connect to + */ + public void connect(UnixSocketAddress address) throws IOException { + if (connected) close(); + this.sock = native_connect(address.path, address.abs); + this.os = new USOutputStream(this.sock, this); + this.is = new USInputStream(this.sock, this); + this.address = address; + this.connected = true; + this.closed = false; + this.is.setBlocking(blocking); + } + + /** + * Connect the socket to this address. + * + * @param address The Unix Socket address to connect to + */ + public void connect(String address) throws IOException { + connect(new UnixSocketAddress(address)); + } + + public void finalize() { + try { + close(); + } catch (IOException IOe) { + } + } + + /** + * Closes the connection. + */ + public synchronized void close() throws IOException { + if (Debug.debug) Debug.print(Debug.INFO, "Closing socket"); + native_close(sock); + sock = 0; + this.closed = true; + this.connected = false; + os = null; + is = null; + } + + /** + * Returns an InputStream for reading from the socket. + * + * @return An InputStream connected to this socket. + */ + public InputStream getInputStream() { + return is; + } + + /** + * Returns an OutputStream for writing to the socket. + * + * @return An OutputStream connected to this socket. + */ + public OutputStream getOutputStream() { + return os; + } + + /** + * Returns the address this socket is connected to. + * Returns null if the socket is unconnected. + * + * @return The UnixSocketAddress the socket is connected to + */ + public UnixSocketAddress getAddress() { + return address; + } + + /** + * Send a single byte of data with credentials. + * (Works on BSDs) + * + * @param data The byte of data to send. + */ + public void sendCredentialByte(byte data) throws IOException { + if (!connected) throw new NotConnectedException(); + native_send_creds(sock, data); + } + + /** + * Receive a single byte of data, with credentials. + * (Works on BSDs) + * + * @param data The byte of data to send. + * @see getPeerUID + * @see getPeerPID + * @see getPeerGID + */ + public byte recvCredentialByte() throws IOException { + if (!connected) throw new NotConnectedException(); + int[] creds = new int[]{-1, -1, -1}; + byte data = native_recv_creds(sock, creds); + pid = creds[0]; + uid = creds[1]; + gid = creds[2]; + return data; + } + + /** + * Get the credential passing status. + * (only effective on linux) + * + * @return The current status of credential passing. + * @see setPassCred + */ + public boolean getPassCred() { + return passcred; + } + + /** + * Return the uid of the remote process. + * Some data must have been received on the socket to do this. + * Either setPassCred must be called on Linux first, or recvCredentialByte + * on BSD. + * + * @return the UID or -1 if it is not available + */ + public int getPeerUID() { + if (-1 == uid) + uid = native_getUID(sock); + return uid; + } + + /** + * Return the gid of the remote process. + * Some data must have been received on the socket to do this. + * Either setPassCred must be called on Linux first, or recvCredentialByte + * on BSD. + * + * @return the GID or -1 if it is not available + */ + public int getPeerGID() { + if (-1 == gid) + gid = native_getGID(sock); + return gid; + } + + /** + * Return the pid of the remote process. + * Some data must have been received on the socket to do this. + * Either setPassCred must be called on Linux first, or recvCredentialByte + * on BSD. + * + * @return the PID or -1 if it is not available + */ + public int getPeerPID() { + if (-1 == pid) + pid = native_getPID(sock); + return pid; + } + + /** + * Set the credential passing status. + * (Only does anything on linux, for other OS, you need + * to use send/recv credentials) + * + * @param enable Set to true for credentials to be passed. + */ + public void setPassCred(boolean enable) throws IOException { + native_set_pass_cred(sock, enable); + passcred = enable; + } + + /** + * Get the blocking mode. + * + * @return true if reads are blocking. + * @see setBlocking + */ + public boolean getBlocking() { + return blocking; + } + + /** + * Set the blocking mode. + * + * @param enable Set to false for non-blocking reads. + */ + public void setBlocking(boolean enable) { + blocking = enable; + if (null != is) is.setBlocking(enable); + } + + /** + * Check the socket status. + * + * @return true if closed. + */ + public boolean isClosed() { + return closed; + } + + /** + * Check the socket status. + * + * @return true if connected. + */ + public boolean isConnected() { + return connected; + } + + /** + * Check the socket status. + * + * @return true if the input stream has been shutdown + */ + public boolean isInputShutdown() { + return is.isClosed(); + } + + /** + * Check the socket status. + * + * @return true if the output stream has been shutdown + */ + public boolean isOutputShutdown() { + return os.isClosed(); + } + + /** + * Shuts down the input stream. + * Subsequent reads on the associated InputStream will fail. + */ + public void shutdownInput() { + is.closed = true; + } + + /** + * Shuts down the output stream. + * Subsequent writes to the associated OutputStream will fail. + */ + public void shutdownOutput() { + os.closed = true; + } + + /** + * Set timeout of read requests. + */ + public void setSoTimeout(int timeout) { + is.setSoTimeout(timeout); + } +} diff --git a/federation/sssd/src/main/java/cx/ath/matthew/unix/UnixSocketAddress.java b/federation/sssd/src/main/java/cx/ath/matthew/unix/UnixSocketAddress.java new file mode 100644 index 0000000000..0baba479bb --- /dev/null +++ b/federation/sssd/src/main/java/cx/ath/matthew/unix/UnixSocketAddress.java @@ -0,0 +1,86 @@ +/* + * Java Unix Sockets Library + * + * Copyright (c) Matthew Johnson 2004 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * To Contact the author, please email src@matthew.ath.cx + * + */ +package cx.ath.matthew.unix; + +/** + * Represents an address for a Unix Socket + */ +public class UnixSocketAddress { + String path; + boolean abs; + + /** + * Create the address. + * + * @param path The path to the Unix Socket. + * @param abs True if this should be an abstract socket. + */ + public UnixSocketAddress(String path, boolean abs) { + this.path = path; + this.abs = abs; + } + + /** + * Create the address. + * + * @param path The path to the Unix Socket. + */ + public UnixSocketAddress(String path) { + this.path = path; + this.abs = false; + } + + /** + * Return the path. + */ + public String getPath() { + return path; + } + + /** + * Returns true if this an address for an abstract socket. + */ + public boolean isAbstract() { + return abs; + } + + /** + * Return the Address as a String. + */ + public String toString() { + return "unix" + (abs ? ":abstract" : "") + ":path=" + path; + } + + public boolean equals(Object o) { + if (!(o instanceof UnixSocketAddress)) return false; + return ((UnixSocketAddress) o).path.equals(this.path); + } + + public int hashCode() { + return path.hashCode(); + } +} diff --git a/federation/sssd/src/main/java/cx/ath/matthew/utils/Hexdump.java b/federation/sssd/src/main/java/cx/ath/matthew/utils/Hexdump.java new file mode 100644 index 0000000000..63f37194db --- /dev/null +++ b/federation/sssd/src/main/java/cx/ath/matthew/utils/Hexdump.java @@ -0,0 +1,147 @@ +/* + * Java Hexdump Library + * + * Copyright (c) Matthew Johnson 2005 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * To Contact the author, please email src@matthew.ath.cx + * + */ + +package cx.ath.matthew.utils; + +import java.io.PrintStream; + +public class Hexdump { + public static final char[] hexchars = new char[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; + + public static String toHex(byte[] buf) { + return toHex(buf, 0, buf.length); + } + + public static String toHex(byte[] buf, int ofs, int len) { + StringBuffer sb = new StringBuffer(); + int j = ofs + len; + for (int i = ofs; i < j; i++) { + if (i < buf.length) { + sb.append(hexchars[(buf[i] & 0xF0) >> 4]); + sb.append(hexchars[buf[i] & 0x0F]); + sb.append(' '); + } else { + sb.append(' '); + sb.append(' '); + sb.append(' '); + } + } + return sb.toString(); + } + + public static String toAscii(byte[] buf) { + return toAscii(buf, 0, buf.length); + } + + public static String toAscii(byte[] buf, int ofs, int len) { + StringBuffer sb = new StringBuffer(); + int j = ofs + len; + for (int i = ofs; i < j; i++) { + if (i < buf.length) { + if (20 <= buf[i] && 126 >= buf[i]) + sb.append((char) buf[i]); + else + sb.append('.'); + } else + sb.append(' '); + } + return sb.toString(); + } + + public static String format(byte[] buf) { + return format(buf, 80); + } + + public static String format(byte[] buf, int width) { + int bs = (width - 8) / 4; + int i = 0; + StringBuffer sb = new StringBuffer(); + do { + for (int j = 0; j < 6; j++) { + sb.append(hexchars[(i << (j * 4) & 0xF00000) >> 20]); + } + sb.append('\t'); + sb.append(toHex(buf, i, bs)); + sb.append(' '); + sb.append(toAscii(buf, i, bs)); + sb.append('\n'); + i += bs; + } while (i < buf.length); + return sb.toString(); + } + + public static void print(byte[] buf) { + print(buf, System.err); + } + + public static void print(byte[] buf, int width) { + print(buf, width, System.err); + } + + public static void print(byte[] buf, int width, PrintStream out) { + out.print(format(buf, width)); + } + + public static void print(byte[] buf, PrintStream out) { + out.print(format(buf)); + } + + /** + * Returns a string which can be written to a Java source file as part + * of a static initializer for a byte array. + * Returns data in the format 0xAB, 0xCD, .... + * use like: + * javafile.print("byte[] data = {") + * javafile.print(Hexdump.toByteArray(data)); + * javafile.println("};"); + */ + public static String toByteArray(byte[] buf) { + return toByteArray(buf, 0, buf.length); + } + + /** + * Returns a string which can be written to a Java source file as part + * of a static initializer for a byte array. + * Returns data in the format 0xAB, 0xCD, .... + * use like: + * javafile.print("byte[] data = {") + * javafile.print(Hexdump.toByteArray(data)); + * javafile.println("};"); + */ + public static String toByteArray(byte[] buf, int ofs, int len) { + StringBuffer sb = new StringBuffer(); + for (int i = ofs; i < len && i < buf.length; i++) { + sb.append('0'); + sb.append('x'); + sb.append(hexchars[(buf[i] & 0xF0) >> 4]); + sb.append(hexchars[buf[i] & 0x0F]); + if ((i + 1) < len && (i + 1) < buf.length) + sb.append(','); + } + return sb.toString(); + } +} diff --git a/federation/sssd/src/main/java/org/freedesktop/DBus.java b/federation/sssd/src/main/java/org/freedesktop/DBus.java new file mode 100644 index 0000000000..b7a16877ae --- /dev/null +++ b/federation/sssd/src/main/java/org/freedesktop/DBus.java @@ -0,0 +1,534 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop; + +import org.freedesktop.dbus.DBusInterface; +import org.freedesktop.dbus.DBusSignal; +import org.freedesktop.dbus.Position; +import org.freedesktop.dbus.Struct; +import org.freedesktop.dbus.Tuple; +import org.freedesktop.dbus.UInt16; +import org.freedesktop.dbus.UInt32; +import org.freedesktop.dbus.UInt64; +import org.freedesktop.dbus.Variant; +import org.freedesktop.dbus.exceptions.DBusException; +import org.freedesktop.dbus.exceptions.DBusExecutionException; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.List; +import java.util.Map; + +public interface DBus extends DBusInterface { + + String BUSNAME = "org.freedesktop.DBus"; + String OBJECTPATH = "/org/freedesktop/DBus"; + + int DBUS_NAME_FLAG_ALLOW_REPLACEMENT = 0x01; + int DBUS_NAME_FLAG_REPLACE_EXISTING = 0x02; + int DBUS_NAME_FLAG_DO_NOT_QUEUE = 0x04; + int DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER = 1; + int DBUS_REQUEST_NAME_REPLY_IN_QUEUE = 2; + int DBUS_REQUEST_NAME_REPLY_EXISTS = 3; + int DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER = 4; + int DBUS_RELEASEME_REPLY_RELEASED = 1; + int DBUS_RELEASE_NAME_REPLY_NON_EXISTANT = 2; + int DBUS_RELEASE_NAME_REPLY_NOT_OWNER = 3; + int DBUS_START_REPLY_SUCCESS = 1; + int DBUS_START_REPLY_ALREADY_RUNNING = 2; + + /** + * All DBus Applications should respond to the Ping method on this interface + */ + public interface Peer extends DBusInterface { + public void Ping(); + } + + /** + * Objects can provide introspection data via this interface and method. + * See the Introspection Format. + */ + public interface Introspectable extends DBusInterface { + /** + * @return The XML introspection data for this object + */ + public String Introspect(); + } + + /** + * A standard properties interface. + */ + public interface Properties extends DBusInterface { + /** + * Get the value for the given property. + * + * @param interface_name The interface this property is associated with. + * @param property_name The name of the property. + * @return The value of the property (may be any valid DBus type). + */ + public A Get(String interface_name, String property_name); + + /** + * Set the value for the given property. + * + * @param interface_name The interface this property is associated with. + * @param property_name The name of the property. + * @param value The new value of the property (may be any valid DBus type). + */ + public void Set(String interface_name, String property_name, A value); + + /** + * Get all properties and values. + * + * @param interface_name The interface the properties is associated with. + * @return The properties mapped to their values. + */ + public Map GetAll(String interface_name); + } + + /** + * Messages generated locally in the application. + */ + public interface Local extends DBusInterface { + public class Disconnected extends DBusSignal { + public Disconnected(String path) throws DBusException { + super(path); + } + } + } + + /** + * Initial message to register ourselves on the Bus. + * + * @return The unique name of this connection to the Bus. + */ + public String Hello(); + + /** + * Lists all connected names on the Bus. + * + * @return An array of all connected names. + */ + public String[] ListNames(); + + /** + * Determine if a name has an owner. + * + * @param name The name to query. + * @return true if the name has an owner. + */ + public boolean NameHasOwner(String name); + + /** + * Get the connection unique name that owns the given name. + * + * @param name The name to query. + * @return The connection which owns the name. + */ + public String GetNameOwner(String name); + + /** + * Get the Unix UID that owns a connection name. + * + * @param connection_name The connection name. + * @return The Unix UID that owns it. + */ + public UInt32 GetConnectionUnixUser(String connection_name); + + /** + * Start a service. If the given service is not provided + * by any application, it will be started according to the .service file + * for that service. + * + * @param name The service name to start. + * @param flags Unused. + * @return DBUS_START_REPLY constants. + */ + public UInt32 StartServiceByName(String name, UInt32 flags); + + /** + * Request a name on the bus. + * + * @param name The name to request. + * @param flags DBUS_NAME flags. + * @return DBUS_REQUEST_NAME_REPLY constants. + */ + public UInt32 RequestName(String name, UInt32 flags); + + /** + * Release a name on the bus. + * + * @param name The name to release. + * @return DBUS_RELEASE_NAME_REPLY constants. + */ + public UInt32 ReleaseName(String name); + + /** + * Add a match rule. + * Will cause you to receive messages that aren't directed to you which + * match this rule. + * + * @param matchrule The Match rule as a string. Format Undocumented. + */ + public void AddMatch(String matchrule) throws Error.MatchRuleInvalid; + + /** + * Remove a match rule. + * Will cause you to stop receiving messages that aren't directed to you which + * match this rule. + * + * @param matchrule The Match rule as a string. Format Undocumented. + */ + public void RemoveMatch(String matchrule) throws Error.MatchRuleInvalid; + + /** + * List the connections currently queued for a name. + * + * @param name The name to query + * @return A list of unique connection IDs. + */ + public String[] ListQueuedOwners(String name); + + /** + * Returns the proccess ID associated with a connection. + * + * @param connection_name The name of the connection + * @return The PID of the connection. + */ + public UInt32 GetConnectionUnixProcessID(String connection_name); + + /** + * Does something undocumented. + */ + public Byte[] GetConnectionSELinuxSecurityContext(String a); + + /** + * Does something undocumented. + */ + public void ReloadConfig(); + + /** + * Signal sent when the owner of a name changes + */ + public class NameOwnerChanged extends DBusSignal { + public final String name; + public final String old_owner; + public final String new_owner; + + public NameOwnerChanged(String path, String name, String old_owner, String new_owner) throws DBusException { + super(path, new Object[]{name, old_owner, new_owner}); + this.name = name; + this.old_owner = old_owner; + this.new_owner = new_owner; + } + } + + /** + * Signal sent to a connection when it loses a name + */ + public class NameLost extends DBusSignal { + public final String name; + + public NameLost(String path, String name) throws DBusException { + super(path, name); + this.name = name; + } + } + + /** + * Signal sent to a connection when it aquires a name + */ + public class NameAcquired extends DBusSignal { + public final String name; + + public NameAcquired(String path, String name) throws DBusException { + super(path, name); + this.name = name; + } + } + + /** + * Contains standard errors that can be thrown from methods. + */ + public interface Error { + /** + * Thrown if the method called was unknown on the remote object + */ + @SuppressWarnings("serial") + public class UnknownMethod extends DBusExecutionException { + public UnknownMethod(String message) { + super(message); + } + } + + /** + * Thrown if the object was unknown on a remote connection + */ + @SuppressWarnings("serial") + public class UnknownObject extends DBusExecutionException { + public UnknownObject(String message) { + super(message); + } + } + + /** + * Thrown if the requested service was not available + */ + @SuppressWarnings("serial") + public class ServiceUnknown extends DBusExecutionException { + public ServiceUnknown(String message) { + super(message); + } + } + + /** + * Thrown if the match rule is invalid + */ + @SuppressWarnings("serial") + public class MatchRuleInvalid extends DBusExecutionException { + public MatchRuleInvalid(String message) { + super(message); + } + } + + /** + * Thrown if there is no reply to a method call + */ + @SuppressWarnings("serial") + public class NoReply extends DBusExecutionException { + public NoReply(String message) { + super(message); + } + } + + /** + * Thrown if a message is denied due to a security policy + */ + @SuppressWarnings("serial") + public class AccessDenied extends DBusExecutionException { + public AccessDenied(String message) { + super(message); + } + } + } + + /** + * Description of the interface or method, returned in the introspection data + */ + @Retention(RetentionPolicy.RUNTIME) + public @interface Description { + String value(); + } + + /** + * Indicates that a DBus interface or method is deprecated + */ + @Retention(RetentionPolicy.RUNTIME) + public @interface Deprecated { + } + + /** + * Contains method-specific annotations + */ + public interface Method { + /** + * Methods annotated with this do not send a reply + */ + @Target(ElementType.METHOD) + @Retention(RetentionPolicy.RUNTIME) + public @interface NoReply { + } + + /** + * Give an error that the method can return + */ + @Target(ElementType.METHOD) + @Retention(RetentionPolicy.RUNTIME) + public @interface Error { + String value(); + } + } + + /** + * Contains GLib-specific annotations + */ + public interface GLib { + /** + * Define a C symbol to map to this method. Used by GLib only + */ + @Target(ElementType.METHOD) + @Retention(RetentionPolicy.RUNTIME) + public @interface CSymbol { + String value(); + } + } + + /** + * Contains Binding-test interfaces + */ + public interface Binding { + public interface SingleTests extends DBusInterface { + @Description("Returns the sum of the values in the input list") + public UInt32 Sum(byte[] a); + } + + public interface TestClient extends DBusInterface { + @Description("when the trigger signal is received, this method should be called on the sending process/object.") + public void Response(UInt16 a, double b); + + @Description("Causes a callback") + public static class Trigger extends DBusSignal { + public final UInt16 a; + public final double b; + + public Trigger(String path, UInt16 a, double b) throws DBusException { + super(path, a, b); + this.a = a; + this.b = b; + } + } + + } + + public interface Tests extends DBusInterface { + @Description("Returns whatever it is passed") + public Variant Identity(Variant input); + + @Description("Returns whatever it is passed") + public byte IdentityByte(byte input); + + @Description("Returns whatever it is passed") + public boolean IdentityBool(boolean input); + + @Description("Returns whatever it is passed") + public short IdentityInt16(short input); + + @Description("Returns whatever it is passed") + public UInt16 IdentityUInt16(UInt16 input); + + @Description("Returns whatever it is passed") + public int IdentityInt32(int input); + + @Description("Returns whatever it is passed") + public UInt32 IdentityUInt32(UInt32 input); + + @Description("Returns whatever it is passed") + public long IdentityInt64(long input); + + @Description("Returns whatever it is passed") + public UInt64 IdentityUInt64(UInt64 input); + + @Description("Returns whatever it is passed") + public double IdentityDouble(double input); + + @Description("Returns whatever it is passed") + public String IdentityString(String input); + + @Description("Returns whatever it is passed") + public Variant[] IdentityArray(Variant[] input); + + @Description("Returns whatever it is passed") + public byte[] IdentityByteArray(byte[] input); + + @Description("Returns whatever it is passed") + public boolean[] IdentityBoolArray(boolean[] input); + + @Description("Returns whatever it is passed") + public short[] IdentityInt16Array(short[] input); + + @Description("Returns whatever it is passed") + public UInt16[] IdentityUInt16Array(UInt16[] input); + + @Description("Returns whatever it is passed") + public int[] IdentityInt32Array(int[] input); + + @Description("Returns whatever it is passed") + public UInt32[] IdentityUInt32Array(UInt32[] input); + + @Description("Returns whatever it is passed") + public long[] IdentityInt64Array(long[] input); + + @Description("Returns whatever it is passed") + public UInt64[] IdentityUInt64Array(UInt64[] input); + + @Description("Returns whatever it is passed") + public double[] IdentityDoubleArray(double[] input); + + @Description("Returns whatever it is passed") + public String[] IdentityStringArray(String[] input); + + @Description("Returns the sum of the values in the input list") + public long Sum(int[] a); + + @Description("Given a map of A => B, should return a map of B => a list of all the As which mapped to B") + public Map> InvertMapping(Map a); + + @Description("This method returns the contents of a struct as separate values") + public Triplet DeStruct(TestStruct a); + + @Description("Given any compound type as a variant, return all the primitive types recursively contained within as an array of variants") + public List> Primitize(Variant a); + + @Description("inverts it's input") + public boolean Invert(boolean a); + + @Description("triggers sending of a signal from the supplied object with the given parameter") + public void Trigger(String a, UInt64 b); + + @Description("Causes the server to exit") + public void Exit(); + } + + public interface TestSignals extends DBusInterface { + @Description("Sent in response to a method call") + public static class Triggered extends DBusSignal { + public final UInt64 a; + + public Triggered(String path, UInt64 a) throws DBusException { + super(path, a); + this.a = a; + } + } + } + + public final class Triplet extends Tuple { + @Position(0) + public final A a; + @Position(1) + public final B b; + @Position(2) + public final C c; + + public Triplet(A a, B b, C c) { + this.a = a; + this.b = b; + this.c = c; + } + } + + public final class TestStruct extends Struct { + @Position(0) + public final String a; + @Position(1) + public final UInt32 b; + @Position(2) + public final Short c; + + public TestStruct(String a, UInt32 b, Short c) { + this.a = a; + this.b = b; + this.c = c; + } + } + } +} \ No newline at end of file diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/AbstractConnection.java b/federation/sssd/src/main/java/org/freedesktop/dbus/AbstractConnection.java new file mode 100644 index 0000000000..d1d7f51fb5 --- /dev/null +++ b/federation/sssd/src/main/java/org/freedesktop/dbus/AbstractConnection.java @@ -0,0 +1,1059 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; + +import cx.ath.matthew.debug.Debug; +import org.freedesktop.DBus; +import org.freedesktop.dbus.exceptions.DBusException; +import org.freedesktop.dbus.exceptions.DBusExecutionException; +import org.freedesktop.dbus.exceptions.FatalDBusException; +import org.freedesktop.dbus.exceptions.FatalException; +import org.freedesktop.dbus.exceptions.NotConnected; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Type; +import java.text.MessageFormat; +import java.text.ParseException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.Map; +import java.util.Properties; +import java.util.Vector; +import java.util.regex.Pattern; + +import static org.freedesktop.dbus.Gettext.getString; + + +/** + * Handles a connection to DBus. + */ +public abstract class AbstractConnection { + protected class FallbackContainer { + private Map fallbacks = new HashMap(); + + public synchronized void add(String path, ExportedObject eo) { + if (Debug.debug) Debug.print(Debug.DEBUG, "Adding fallback on " + path + " of " + eo); + fallbacks.put(path.split("/"), eo); + } + + public synchronized void remove(String path) { + if (Debug.debug) Debug.print(Debug.DEBUG, "Removing fallback on " + path); + fallbacks.remove(path.split("/")); + } + + public synchronized ExportedObject get(String path) { + int best = 0; + int i = 0; + ExportedObject bestobject = null; + String[] pathel = path.split("/"); + for (String[] fbpath : fallbacks.keySet()) { + if (Debug.debug) + Debug.print(Debug.VERBOSE, "Trying fallback path " + Arrays.deepToString(fbpath) + " to match " + Arrays.deepToString(pathel)); + for (i = 0; i < pathel.length && i < fbpath.length; i++) + if (!pathel[i].equals(fbpath[i])) break; + if (i > 0 && i == fbpath.length && i > best) + bestobject = fallbacks.get(fbpath); + if (Debug.debug) Debug.print(Debug.VERBOSE, "Matches " + i + " bestobject now " + bestobject); + } + if (Debug.debug) Debug.print(Debug.DEBUG, "Found fallback for " + path + " of " + bestobject); + return bestobject; + } + } + + protected class _thread extends Thread { + public _thread() { + setName("DBusConnection"); + } + + public void run() { + try { + Message m = null; + while (_run) { + m = null; + + // read from the wire + try { + // this blocks on outgoing being non-empty or a message being available. + m = readIncoming(); + if (m != null) { + if (Debug.debug) Debug.print(Debug.VERBOSE, "Got Incoming Message: " + m); + synchronized (this) { + notifyAll(); + } + + if (m instanceof DBusSignal) + handleMessage((DBusSignal) m); + else if (m instanceof MethodCall) + handleMessage((MethodCall) m); + else if (m instanceof MethodReturn) + handleMessage((MethodReturn) m); + else if (m instanceof Error) + handleMessage((Error) m); + + m = null; + } + } catch (Exception e) { + if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, e); + if (e instanceof FatalException) { + disconnect(); + } + } + + } + synchronized (this) { + notifyAll(); + } + } catch (Exception e) { + if (Debug.debug && EXCEPTION_DEBUG) Debug.print(Debug.ERR, e); + } + } + } + + private class _globalhandler implements org.freedesktop.DBus.Peer, org.freedesktop.DBus.Introspectable { + private String objectpath; + + public _globalhandler() { + this.objectpath = null; + } + + public _globalhandler(String objectpath) { + this.objectpath = objectpath; + } + + public boolean isRemote() { + return false; + } + + public void Ping() { + return; + } + + public String Introspect() { + String intro = objectTree.Introspect(objectpath); + if (null == intro) { + ExportedObject eo = fallbackcontainer.get(objectpath); + if (null != eo) intro = eo.introspectiondata; + } + if (null == intro) + throw new DBus.Error.UnknownObject("Introspecting on non-existant object"); + else return + "\n" + intro; + } + } + + protected class _workerthread extends Thread { + private boolean _run = true; + + public void halt() { + _run = false; + } + + public void run() { + while (_run) { + Runnable r = null; + synchronized (runnables) { + while (runnables.size() == 0 && _run) + try { + runnables.wait(); + } catch (InterruptedException Ie) { + } + if (runnables.size() > 0) + r = runnables.removeFirst(); + } + if (null != r) r.run(); + } + } + } + + private class _sender extends Thread { + public _sender() { + setName("Sender"); + } + + public void run() { + Message m = null; + + if (Debug.debug) Debug.print(Debug.INFO, "Monitoring outbound queue"); + // block on the outbound queue and send from it + while (_run) { + if (null != outgoing) synchronized (outgoing) { + if (Debug.debug) Debug.print(Debug.VERBOSE, "Blocking"); + while (outgoing.size() == 0 && _run) + try { + outgoing.wait(); + } catch (InterruptedException Ie) { + } + if (Debug.debug) Debug.print(Debug.VERBOSE, "Notified"); + if (outgoing.size() > 0) + m = outgoing.remove(); + if (Debug.debug) Debug.print(Debug.DEBUG, "Got message: " + m); + } + if (null != m) + sendMessage(m); + m = null; + } + + if (Debug.debug) Debug.print(Debug.INFO, "Flushing outbound queue and quitting"); + // flush the outbound queue before disconnect. + if (null != outgoing) do { + EfficientQueue ogq = outgoing; + synchronized (ogq) { + outgoing = null; + } + if (!ogq.isEmpty()) + m = ogq.remove(); + else m = null; + sendMessage(m); + } while (null != m); + + // close the underlying streams + } + } + + /** + * Timeout in us on checking the BUS for incoming messages and sending outgoing messages + */ + protected static final int TIMEOUT = 100000; + /** + * Initial size of the pending calls map + */ + private static final int PENDING_MAP_INITIAL_SIZE = 10; + static final String BUSNAME_REGEX = "^[-_a-zA-Z][-_a-zA-Z0-9]*(\\.[-_a-zA-Z][-_a-zA-Z0-9]*)*$"; + static final String CONNID_REGEX = "^:[0-9]*\\.[0-9]*$"; + static final String OBJECT_REGEX = "^/([-_a-zA-Z0-9]+(/[-_a-zA-Z0-9]+)*)?$"; + static final byte THREADCOUNT = 4; + static final int MAX_ARRAY_LENGTH = 67108864; + static final int MAX_NAME_LENGTH = 255; + protected Map exportedObjects; + private ObjectTree objectTree; + private _globalhandler _globalhandlerreference; + protected Map importedObjects; + protected Map>> handledSignals; + protected EfficientMap pendingCalls; + protected Map> pendingCallbacks; + protected Map> pendingCallbackReplys; + protected LinkedList runnables; + protected LinkedList<_workerthread> workers; + protected FallbackContainer fallbackcontainer; + protected boolean _run; + EfficientQueue outgoing; + LinkedList pendingErrors; + private static final Map infomap = new HashMap(); + protected _thread thread; + protected _sender sender; + protected Transport transport; + protected String addr; + protected boolean weakreferences = false; + static final Pattern dollar_pattern = Pattern.compile("[$]"); + public static final boolean EXCEPTION_DEBUG; + static final boolean FLOAT_SUPPORT; + protected boolean connected = false; + + static { + FLOAT_SUPPORT = (null != System.getenv("DBUS_JAVA_FLOATS")); + EXCEPTION_DEBUG = (null != System.getenv("DBUS_JAVA_EXCEPTION_DEBUG")); + if (EXCEPTION_DEBUG) { + Debug.print("Debugging of internal exceptions enabled"); + Debug.setThrowableTraces(true); + } + if (Debug.debug) { + File f = new File("debug.conf"); + if (f.exists()) { + Debug.print("Loading debug config file: " + f); + try { + Debug.loadConfig(f); + } catch (IOException IOe) { + } + } else { + Properties p = new Properties(); + p.setProperty("ALL", "INFO"); + Debug.print("debug config file " + f + " does not exist, not loading."); + } + Debug.setHexDump(true); + } + } + + protected AbstractConnection(String address) throws DBusException { + exportedObjects = new HashMap(); + importedObjects = new HashMap(); + _globalhandlerreference = new _globalhandler(); + synchronized (exportedObjects) { + exportedObjects.put(null, new ExportedObject(_globalhandlerreference, weakreferences)); + } + handledSignals = new HashMap>>(); + pendingCalls = new EfficientMap(PENDING_MAP_INITIAL_SIZE); + outgoing = new EfficientQueue(PENDING_MAP_INITIAL_SIZE); + pendingCallbacks = new HashMap>(); + pendingCallbackReplys = new HashMap>(); + pendingErrors = new LinkedList(); + runnables = new LinkedList(); + workers = new LinkedList<_workerthread>(); + objectTree = new ObjectTree(); + fallbackcontainer = new FallbackContainer(); + synchronized (workers) { + for (int i = 0; i < THREADCOUNT; i++) { + _workerthread t = new _workerthread(); + t.start(); + workers.add(t); + } + } + _run = true; + addr = address; + } + + protected void listen() { + // start listening + thread = new _thread(); + thread.start(); + sender = new _sender(); + sender.start(); + } + + /** + * Change the number of worker threads to receive method calls and handle signals. + * Default is 4 threads + * + * @param newcount The new number of worker Threads to use. + */ + public void changeThreadCount(byte newcount) { + synchronized (workers) { + if (workers.size() > newcount) { + int n = workers.size() - newcount; + for (int i = 0; i < n; i++) { + _workerthread t = workers.removeFirst(); + t.halt(); + } + } else if (workers.size() < newcount) { + int n = newcount - workers.size(); + for (int i = 0; i < n; i++) { + _workerthread t = new _workerthread(); + t.start(); + workers.add(t); + } + } + } + } + + private void addRunnable(Runnable r) { + synchronized (runnables) { + runnables.add(r); + runnables.notifyAll(); + } + } + + String getExportedObject(DBusInterface i) throws DBusException { + synchronized (exportedObjects) { + for (String s : exportedObjects.keySet()) + if (i.equals(exportedObjects.get(s).object.get())) + return s; + } + + String s = importedObjects.get(i).objectpath; + if (null != s) return s; + + throw new DBusException("Not an object exported or imported by this connection"); + } + + abstract DBusInterface getExportedObject(String source, String path) throws DBusException; + + /** + * Returns a structure with information on the current method call. + * + * @return the DBusCallInfo for this method call, or null if we are not in a method call. + */ + public static DBusCallInfo getCallInfo() { + DBusCallInfo info; + synchronized (infomap) { + info = infomap.get(Thread.currentThread()); + } + return info; + } + + /** + * If set to true the bus will not hold a strong reference to exported objects. + * If they go out of scope they will automatically be unexported from the bus. + * The default is to hold a strong reference, which means objects must be + * explicitly unexported before they will be garbage collected. + */ + public void setWeakReferences(boolean weakreferences) { + this.weakreferences = weakreferences; + } + + /** + * Export an object so that its methods can be called on DBus. + * + * @param objectpath The path to the object we are exposing. MUST be in slash-notation, like "/org/freedesktop/Local", + * and SHOULD end with a capitalised term. Only one object may be exposed on each path at any one time, but an object + * may be exposed on several paths at once. + * @param object The object to export. + * @throws DBusException If the objectpath is already exporting an object. + * or if objectpath is incorrectly formatted, + */ + public void exportObject(String objectpath, DBusInterface object) throws DBusException { + if (null == objectpath || "".equals(objectpath)) + throw new DBusException(getString("missingObjectPath")); + if (!objectpath.matches(OBJECT_REGEX) || objectpath.length() > MAX_NAME_LENGTH) + throw new DBusException(getString("invalidObjectPath") + objectpath); + synchronized (exportedObjects) { + if (null != exportedObjects.get(objectpath)) + throw new DBusException(getString("objectAlreadyExported")); + ExportedObject eo = new ExportedObject(object, weakreferences); + exportedObjects.put(objectpath, eo); + objectTree.add(objectpath, eo, eo.introspectiondata); + } + } + + /** + * Export an object as a fallback object. + * This object will have it's methods invoked for all paths starting + * with this object path. + * + * @param objectprefix The path below which the fallback handles calls. + * MUST be in slash-notation, like "/org/freedesktop/Local", + * @param object The object to export. + * @throws DBusException If the objectpath is incorrectly formatted, + */ + public void addFallback(String objectprefix, DBusInterface object) throws DBusException { + if (null == objectprefix || "".equals(objectprefix)) + throw new DBusException(getString("missingObjectPath")); + if (!objectprefix.matches(OBJECT_REGEX) || objectprefix.length() > MAX_NAME_LENGTH) + throw new DBusException(getString("invalidObjectPath") + objectprefix); + ExportedObject eo = new ExportedObject(object, weakreferences); + fallbackcontainer.add(objectprefix, eo); + } + + /** + * Remove a fallback + * + * @param objectprefix The prefix to remove the fallback for. + */ + public void removeFallback(String objectprefix) { + fallbackcontainer.remove(objectprefix); + } + + /** + * Stop Exporting an object + * + * @param objectpath The objectpath to stop exporting. + */ + public void unExportObject(String objectpath) { + synchronized (exportedObjects) { + exportedObjects.remove(objectpath); + objectTree.remove(objectpath); + } + } + /** + * Return a reference to a remote object. + * This method will resolve the well known name (if given) to a unique bus name when you call it. + * This means that if a well known name is released by one process and acquired by another calls to + * objects gained from this method will continue to operate on the original process. + * @param busname The bus name to connect to. Usually a well known bus name in dot-notation (such as "org.freedesktop.local") + * or may be a DBus address such as ":1-16". + * @param objectpath The path on which the process is exporting the object.$ + * @param type The interface they are exporting it on. This type must have the same full class name and exposed method signatures + * as the interface the remote object is exporting. + * @return A reference to a remote object. + * @throws ClassCastException If type is not a sub-type of DBusInterface + * @throws DBusException If busname or objectpath are incorrectly formatted or type is not in a package. + */ + /** + * Send a signal. + * + * @param signal The signal to send. + */ + public void sendSignal(DBusSignal signal) { + queueOutgoing(signal); + } + + void queueOutgoing(Message m) { + synchronized (outgoing) { + if (null == outgoing) return; + outgoing.add(m); + if (Debug.debug) Debug.print(Debug.DEBUG, "Notifying outgoing thread"); + outgoing.notifyAll(); + } + } + + /** + * Remove a Signal Handler. + * Stops listening for this signal. + * + * @param type The signal to watch for. + * @throws DBusException If listening for the signal on the bus failed. + * @throws ClassCastException If type is not a sub-type of DBusSignal. + */ + public void removeSigHandler(Class type, DBusSigHandler handler) throws DBusException { + if (!DBusSignal.class.isAssignableFrom(type)) throw new ClassCastException(getString("notDBusSignal")); + removeSigHandler(new DBusMatchRule(type), handler); + } + + /** + * Remove a Signal Handler. + * Stops listening for this signal. + * + * @param type The signal to watch for. + * @param object The object emitting the signal. + * @throws DBusException If listening for the signal on the bus failed. + * @throws ClassCastException If type is not a sub-type of DBusSignal. + */ + public void removeSigHandler(Class type, DBusInterface object, DBusSigHandler handler) throws DBusException { + if (!DBusSignal.class.isAssignableFrom(type)) throw new ClassCastException(getString("notDBusSignal")); + String objectpath = importedObjects.get(object).objectpath; + if (!objectpath.matches(OBJECT_REGEX) || objectpath.length() > MAX_NAME_LENGTH) + throw new DBusException(getString("invalidObjectPath") + objectpath); + removeSigHandler(new DBusMatchRule(type, null, objectpath), handler); + } + + protected abstract void removeSigHandler(DBusMatchRule rule, DBusSigHandler handler) throws DBusException; + + /** + * Add a Signal Handler. + * Adds a signal handler to call when a signal is received which matches the specified type and name. + * + * @param type The signal to watch for. + * @param handler The handler to call when a signal is received. + * @throws DBusException If listening for the signal on the bus failed. + * @throws ClassCastException If type is not a sub-type of DBusSignal. + */ + @SuppressWarnings("unchecked") + public void addSigHandler(Class type, DBusSigHandler handler) throws DBusException { + if (!DBusSignal.class.isAssignableFrom(type)) throw new ClassCastException(getString("notDBusSignal")); + addSigHandler(new DBusMatchRule(type), (DBusSigHandler) handler); + } + + /** + * Add a Signal Handler. + * Adds a signal handler to call when a signal is received which matches the specified type, name and object. + * + * @param type The signal to watch for. + * @param object The object from which the signal will be emitted + * @param handler The handler to call when a signal is received. + * @throws DBusException If listening for the signal on the bus failed. + * @throws ClassCastException If type is not a sub-type of DBusSignal. + */ + @SuppressWarnings("unchecked") + public void addSigHandler(Class type, DBusInterface object, DBusSigHandler handler) throws DBusException { + if (!DBusSignal.class.isAssignableFrom(type)) throw new ClassCastException(getString("notDBusSignal")); + String objectpath = importedObjects.get(object).objectpath; + if (!objectpath.matches(OBJECT_REGEX) || objectpath.length() > MAX_NAME_LENGTH) + throw new DBusException(getString("invalidObjectPath") + objectpath); + addSigHandler(new DBusMatchRule(type, null, objectpath), (DBusSigHandler) handler); + } + + protected abstract void addSigHandler(DBusMatchRule rule, DBusSigHandler handler) throws DBusException; + + protected void addSigHandlerWithoutMatch(Class signal, DBusSigHandler handler) throws DBusException { + DBusMatchRule rule = new DBusMatchRule(signal); + SignalTuple key = new SignalTuple(rule.getInterface(), rule.getMember(), rule.getObject(), rule.getSource()); + synchronized (handledSignals) { + Vector> v = handledSignals.get(key); + if (null == v) { + v = new Vector>(); + v.add(handler); + handledSignals.put(key, v); + } else + v.add(handler); + } + } + + /** + * Disconnect from the Bus. + */ + public void disconnect() { + connected = false; + if (Debug.debug) Debug.print(Debug.INFO, "Sending disconnected signal"); + try { + handleMessage(new org.freedesktop.DBus.Local.Disconnected("/")); + } catch (Exception ee) { + if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, ee); + } + + if (Debug.debug) Debug.print(Debug.INFO, "Disconnecting Abstract Connection"); + // run all pending tasks. + while (runnables.size() > 0) + synchronized (runnables) { + runnables.notifyAll(); + } + + // stop the main thread + _run = false; + + // unblock the sending thread. + synchronized (outgoing) { + outgoing.notifyAll(); + } + + // disconnect from the trasport layer + try { + if (null != transport) { + transport.disconnect(); + transport = null; + } + } catch (IOException IOe) { + if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, IOe); + } + + // stop all the workers + synchronized (workers) { + for (_workerthread t : workers) + t.halt(); + } + + // make sure none are blocking on the runnables queue still + synchronized (runnables) { + runnables.notifyAll(); + } + } + + public void finalize() { + disconnect(); + } + + /** + * Return any DBus error which has been received. + * + * @return A DBusExecutionException, or null if no error is pending. + */ + public DBusExecutionException getError() { + synchronized (pendingErrors) { + if (pendingErrors.size() == 0) return null; + else + return pendingErrors.removeFirst().getException(); + } + } + + /** + * Call a method asynchronously and set a callback. + * This handler will be called in a separate thread. + * + * @param object The remote object on which to call the method. + * @param m The name of the method on the interface to call. + * @param callback The callback handler. + * @param parameters The parameters to call the method with. + */ + @SuppressWarnings("unchecked") + public void callWithCallback(DBusInterface object, String m, CallbackHandler callback, Object... parameters) { + if (Debug.debug) Debug.print(Debug.VERBOSE, "callWithCallback(" + object + "," + m + ", " + callback); + Class[] types = new Class[parameters.length]; + for (int i = 0; i < parameters.length; i++) + types[i] = parameters[i].getClass(); + RemoteObject ro = importedObjects.get(object); + + try { + Method me; + if (null == ro.iface) + me = object.getClass().getMethod(m, types); + else + me = ro.iface.getMethod(m, types); + RemoteInvocationHandler.executeRemoteMethod(ro, me, this, RemoteInvocationHandler.CALL_TYPE_CALLBACK, callback, parameters); + } catch (DBusExecutionException DBEe) { + if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, DBEe); + throw DBEe; + } catch (Exception e) { + if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, e); + throw new DBusExecutionException(e.getMessage()); + } + } + + /** + * Call a method asynchronously and get a handle with which to get the reply. + * + * @param object The remote object on which to call the method. + * @param m The name of the method on the interface to call. + * @param parameters The parameters to call the method with. + * @return A handle to the call. + */ + @SuppressWarnings("unchecked") + public DBusAsyncReply callMethodAsync(DBusInterface object, String m, Object... parameters) { + Class[] types = new Class[parameters.length]; + for (int i = 0; i < parameters.length; i++) + types[i] = parameters[i].getClass(); + RemoteObject ro = importedObjects.get(object); + + try { + Method me; + if (null == ro.iface) + me = object.getClass().getMethod(m, types); + else + me = ro.iface.getMethod(m, types); + return (DBusAsyncReply) RemoteInvocationHandler.executeRemoteMethod(ro, me, this, RemoteInvocationHandler.CALL_TYPE_ASYNC, null, parameters); + } catch (DBusExecutionException DBEe) { + if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, DBEe); + throw DBEe; + } catch (Exception e) { + if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, e); + throw new DBusExecutionException(e.getMessage()); + } + } + + private void handleMessage(final MethodCall m) throws DBusException { + if (Debug.debug) Debug.print(Debug.DEBUG, "Handling incoming method call: " + m); + + ExportedObject eo = null; + Method meth = null; + Object o = null; + + if (null == m.getInterface() || + m.getInterface().equals("org.freedesktop.DBus.Peer") || + m.getInterface().equals("org.freedesktop.DBus.Introspectable")) { + synchronized (exportedObjects) { + eo = exportedObjects.get(null); + } + if (null != eo && null == eo.object.get()) { + unExportObject(null); + eo = null; + } + if (null != eo) { + meth = eo.methods.get(new MethodTuple(m.getName(), m.getSig())); + } + if (null != meth) + o = new _globalhandler(m.getPath()); + else + eo = null; + } + if (null == o) { + // now check for specific exported functions + + synchronized (exportedObjects) { + eo = exportedObjects.get(m.getPath()); + } + if (null != eo && null == eo.object.get()) { + if (Debug.debug) Debug.print(Debug.INFO, "Unexporting " + m.getPath() + " implicitly"); + unExportObject(m.getPath()); + eo = null; + } + + if (null == eo) { + eo = fallbackcontainer.get(m.getPath()); + } + + if (null == eo) { + try { + queueOutgoing(new Error(m, new DBus.Error.UnknownObject(m.getPath() + getString("notObjectProvidedByProcess")))); + } catch (DBusException DBe) { + } + return; + } + if (Debug.debug) { + Debug.print(Debug.VERBOSE, "Searching for method " + m.getName() + " with signature " + m.getSig()); + Debug.print(Debug.VERBOSE, "List of methods on " + eo + ":"); + for (MethodTuple mt : eo.methods.keySet()) + Debug.print(Debug.VERBOSE, " " + mt + " => " + eo.methods.get(mt)); + } + meth = eo.methods.get(new MethodTuple(m.getName(), m.getSig())); + if (null == meth) { + try { + queueOutgoing(new Error(m, new DBus.Error.UnknownMethod(MessageFormat.format(getString("methodDoesNotExist"), new Object[]{m.getInterface(), m.getName()})))); + } catch (DBusException DBe) { + } + return; + } + o = eo.object.get(); + } + + // now execute it + final Method me = meth; + final Object ob = o; + final boolean noreply = (1 == (m.getFlags() & Message.Flags.NO_REPLY_EXPECTED)); + final DBusCallInfo info = new DBusCallInfo(m); + final AbstractConnection conn = this; + if (Debug.debug) Debug.print(Debug.VERBOSE, "Adding Runnable for method " + meth); + addRunnable(new Runnable() { + private boolean run = false; + + public synchronized void run() { + if (run) return; + run = true; + if (Debug.debug) Debug.print(Debug.DEBUG, "Running method " + me + " for remote call"); + try { + Type[] ts = me.getGenericParameterTypes(); + m.setArgs(Marshalling.deSerializeParameters(m.getParameters(), ts, conn)); + if (Debug.debug) + Debug.print(Debug.VERBOSE, "Deserialised " + Arrays.deepToString(m.getParameters()) + " to types " + Arrays.deepToString(ts)); + } catch (Exception e) { + if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, e); + try { + conn.queueOutgoing(new Error(m, new DBus.Error.UnknownMethod(getString("deSerializationFailure") + e))); + } catch (DBusException DBe) { + } + return; + } + + try { + synchronized (infomap) { + infomap.put(Thread.currentThread(), info); + } + Object result; + try { + if (Debug.debug) + Debug.print(Debug.VERBOSE, "Invoking Method: " + me + " on " + ob + " with parameters " + Arrays.deepToString(m.getParameters())); + result = me.invoke(ob, m.getParameters()); + } catch (InvocationTargetException ITe) { + if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, ITe.getCause()); + throw ITe.getCause(); + } + synchronized (infomap) { + infomap.remove(Thread.currentThread()); + } + if (!noreply) { + MethodReturn reply; + if (Void.TYPE.equals(me.getReturnType())) + reply = new MethodReturn(m, null); + else { + StringBuffer sb = new StringBuffer(); + for (String s : Marshalling.getDBusType(me.getGenericReturnType())) + sb.append(s); + Object[] nr = Marshalling.convertParameters(new Object[]{result}, new Type[]{me.getGenericReturnType()}, conn); + + reply = new MethodReturn(m, sb.toString(), nr); + } + conn.queueOutgoing(reply); + } + } catch (DBusExecutionException DBEe) { + if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, DBEe); + try { + conn.queueOutgoing(new Error(m, DBEe)); + } catch (DBusException DBe) { + } + } catch (Throwable e) { + if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, e); + try { + conn.queueOutgoing(new Error(m, new DBusExecutionException(MessageFormat.format(getString("errorExecutingMethod"), new Object[]{m.getInterface(), m.getName(), e.getMessage()})))); + } catch (DBusException DBe) { + } + } + } + }); + } + + @SuppressWarnings({"unchecked", "deprecation"}) + private void handleMessage(final DBusSignal s) { + if (Debug.debug) Debug.print(Debug.DEBUG, "Handling incoming signal: " + s); + Vector> v = new Vector>(); + synchronized (handledSignals) { + Vector> t; + t = handledSignals.get(new SignalTuple(s.getInterface(), s.getName(), null, null)); + if (null != t) v.addAll(t); + t = handledSignals.get(new SignalTuple(s.getInterface(), s.getName(), s.getPath(), null)); + if (null != t) v.addAll(t); + t = handledSignals.get(new SignalTuple(s.getInterface(), s.getName(), null, s.getSource())); + if (null != t) v.addAll(t); + t = handledSignals.get(new SignalTuple(s.getInterface(), s.getName(), s.getPath(), s.getSource())); + if (null != t) v.addAll(t); + } + if (0 == v.size()) return; + final AbstractConnection conn = this; + for (final DBusSigHandler h : v) { + if (Debug.debug) Debug.print(Debug.VERBOSE, "Adding Runnable for signal " + s + " with handler " + h); + addRunnable(new Runnable() { + private boolean run = false; + + public synchronized void run() { + if (run) return; + run = true; + try { + DBusSignal rs; + if (s instanceof DBusSignal.internalsig || s.getClass().equals(DBusSignal.class)) + rs = s.createReal(conn); + else + rs = s; + ((DBusSigHandler) h).handle(rs); + } catch (DBusException DBe) { + if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, DBe); + try { + conn.queueOutgoing(new Error(s, new DBusExecutionException("Error handling signal " + s.getInterface() + "." + s.getName() + ": " + DBe.getMessage()))); + } catch (DBusException DBe2) { + } + } + } + }); + } + } + + private void handleMessage(final Error err) { + if (Debug.debug) Debug.print(Debug.DEBUG, "Handling incoming error: " + err); + MethodCall m = null; + if (null == pendingCalls) return; + synchronized (pendingCalls) { + if (pendingCalls.contains(err.getReplySerial())) + m = pendingCalls.remove(err.getReplySerial()); + } + if (null != m) { + m.setReply(err); + CallbackHandler cbh = null; + DBusAsyncReply asr = null; + synchronized (pendingCallbacks) { + cbh = pendingCallbacks.remove(m); + if (Debug.debug) Debug.print(Debug.VERBOSE, cbh + " = pendingCallbacks.remove(" + m + ")"); + asr = pendingCallbackReplys.remove(m); + } + // queue callback for execution + if (null != cbh) { + final CallbackHandler fcbh = cbh; + if (Debug.debug) Debug.print(Debug.VERBOSE, "Adding Error Runnable with callback handler " + fcbh); + addRunnable(new Runnable() { + private boolean run = false; + + public synchronized void run() { + if (run) return; + run = true; + try { + if (Debug.debug) Debug.print(Debug.VERBOSE, "Running Error Callback for " + err); + DBusCallInfo info = new DBusCallInfo(err); + synchronized (infomap) { + infomap.put(Thread.currentThread(), info); + } + + fcbh.handleError(err.getException()); + synchronized (infomap) { + infomap.remove(Thread.currentThread()); + } + + } catch (Exception e) { + if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, e); + } + } + }); + } + + } else + synchronized (pendingErrors) { + pendingErrors.addLast(err); + } + } + + @SuppressWarnings("unchecked") + private void handleMessage(final MethodReturn mr) { + if (Debug.debug) Debug.print(Debug.DEBUG, "Handling incoming method return: " + mr); + MethodCall m = null; + if (null == pendingCalls) return; + synchronized (pendingCalls) { + if (pendingCalls.contains(mr.getReplySerial())) + m = pendingCalls.remove(mr.getReplySerial()); + } + if (null != m) { + m.setReply(mr); + mr.setCall(m); + CallbackHandler cbh = null; + DBusAsyncReply asr = null; + synchronized (pendingCallbacks) { + cbh = pendingCallbacks.remove(m); + if (Debug.debug) Debug.print(Debug.VERBOSE, cbh + " = pendingCallbacks.remove(" + m + ")"); + asr = pendingCallbackReplys.remove(m); + } + // queue callback for execution + if (null != cbh) { + final CallbackHandler fcbh = cbh; + final DBusAsyncReply fasr = asr; + if (Debug.debug) + Debug.print(Debug.VERBOSE, "Adding Runnable for method " + fasr.getMethod() + " with callback handler " + fcbh); + addRunnable(new Runnable() { + private boolean run = false; + + public synchronized void run() { + if (run) return; + run = true; + try { + if (Debug.debug) Debug.print(Debug.VERBOSE, "Running Callback for " + mr); + DBusCallInfo info = new DBusCallInfo(mr); + synchronized (infomap) { + infomap.put(Thread.currentThread(), info); + } + + fcbh.handle(RemoteInvocationHandler.convertRV(mr.getSig(), mr.getParameters(), fasr.getMethod(), fasr.getConnection())); + synchronized (infomap) { + infomap.remove(Thread.currentThread()); + } + + } catch (Exception e) { + if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, e); + } + } + }); + } + + } else + try { + queueOutgoing(new Error(mr, new DBusExecutionException(getString("spuriousReply")))); + } catch (DBusException DBe) { + } + } + + protected void sendMessage(Message m) { + try { + if (!connected) throw new NotConnected(getString("disconnected")); + if (m instanceof DBusSignal) + ((DBusSignal) m).appendbody(this); + + if (m instanceof MethodCall) { + if (0 == (m.getFlags() & Message.Flags.NO_REPLY_EXPECTED)) + if (null == pendingCalls) + ((MethodCall) m).setReply(new Error("org.freedesktop.DBus.Local", "org.freedesktop.DBus.Local.disconnected", 0, "s", new Object[]{getString("disconnected")})); + else synchronized (pendingCalls) { + pendingCalls.put(m.getSerial(), (MethodCall) m); + } + } + + transport.mout.writeMessage(m); + + } catch (Exception e) { + if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, e); + if (m instanceof MethodCall && e instanceof NotConnected) + try { + ((MethodCall) m).setReply(new Error("org.freedesktop.DBus.Local", "org.freedesktop.DBus.Local.disconnected", 0, "s", new Object[]{getString("disconnected")})); + } catch (DBusException DBe) { + } + if (m instanceof MethodCall && e instanceof DBusExecutionException) + try { + ((MethodCall) m).setReply(new Error(m, e)); + } catch (DBusException DBe) { + } + else if (m instanceof MethodCall) + try { + if (Debug.debug) Debug.print(Debug.INFO, "Setting reply to " + m + " as an error"); + ((MethodCall) m).setReply(new Error(m, new DBusExecutionException(getString("messageFailedSend") + e.getMessage()))); + } catch (DBusException DBe) { + } + else if (m instanceof MethodReturn) + try { + transport.mout.writeMessage(new Error(m, e)); + } catch (IOException IOe) { + if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, IOe); + } catch (DBusException IOe) { + if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, e); + } + if (e instanceof IOException) disconnect(); + } + } + + private Message readIncoming() throws DBusException { + if (!connected) throw new NotConnected(getString("missingTransport")); + Message m = null; + try { + m = transport.min.readMessage(); + } catch (IOException IOe) { + throw new FatalDBusException(IOe.getMessage()); + } + return m; + } + + /** + * Returns the address this connection is connected to. + */ + public BusAddress getAddress() throws ParseException { + return new BusAddress(addr); + } +} diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/ArrayFrob.java b/federation/sssd/src/main/java/org/freedesktop/dbus/ArrayFrob.java new file mode 100644 index 0000000000..86dfa5282c --- /dev/null +++ b/federation/sssd/src/main/java/org/freedesktop/dbus/ArrayFrob.java @@ -0,0 +1,173 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; + +import cx.ath.matthew.debug.Debug; + +import java.lang.reflect.Array; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Hashtable; +import java.util.List; + +import static org.freedesktop.dbus.Gettext.getString; + +class ArrayFrob { + static Hashtable, Class> primitiveToWrapper = new Hashtable, Class>(); + static Hashtable, Class> wrapperToPrimitive = new Hashtable, Class>(); + + static { + primitiveToWrapper.put(Boolean.TYPE, Boolean.class); + primitiveToWrapper.put(Byte.TYPE, Byte.class); + primitiveToWrapper.put(Short.TYPE, Short.class); + primitiveToWrapper.put(Character.TYPE, Character.class); + primitiveToWrapper.put(Integer.TYPE, Integer.class); + primitiveToWrapper.put(Long.TYPE, Long.class); + primitiveToWrapper.put(Float.TYPE, Float.class); + primitiveToWrapper.put(Double.TYPE, Double.class); + wrapperToPrimitive.put(Boolean.class, Boolean.TYPE); + wrapperToPrimitive.put(Byte.class, Byte.TYPE); + wrapperToPrimitive.put(Short.class, Short.TYPE); + wrapperToPrimitive.put(Character.class, Character.TYPE); + wrapperToPrimitive.put(Integer.class, Integer.TYPE); + wrapperToPrimitive.put(Long.class, Long.TYPE); + wrapperToPrimitive.put(Float.class, Float.TYPE); + wrapperToPrimitive.put(Double.class, Double.TYPE); + + } + + @SuppressWarnings("unchecked") + public static T[] wrap(Object o) throws IllegalArgumentException { + Class ac = o.getClass(); + if (!ac.isArray()) throw new IllegalArgumentException(getString("invalidArray")); + Class cc = ac.getComponentType(); + Class ncc = primitiveToWrapper.get(cc); + if (null == ncc) throw new IllegalArgumentException(getString("notPrimitiveType")); + T[] ns = (T[]) Array.newInstance(ncc, Array.getLength(o)); + for (int i = 0; i < ns.length; i++) + ns[i] = (T) Array.get(o, i); + return ns; + } + + @SuppressWarnings("unchecked") + public static Object unwrap(T[] ns) throws IllegalArgumentException { + Class ac = (Class) ns.getClass(); + Class cc = (Class) ac.getComponentType(); + Class ncc = wrapperToPrimitive.get(cc); + if (null == ncc) throw new IllegalArgumentException(getString("invalidWrapperType")); + Object o = Array.newInstance(ncc, ns.length); + for (int i = 0; i < ns.length; i++) + Array.set(o, i, ns[i]); + return o; + } + + public static List listify(T[] ns) throws IllegalArgumentException { + return Arrays.asList(ns); + } + + @SuppressWarnings("unchecked") + public static List listify(Object o) throws IllegalArgumentException { + if (o instanceof Object[]) return listify((T[]) o); + if (!o.getClass().isArray()) throw new IllegalArgumentException(getString("invalidArray")); + List l = new ArrayList(Array.getLength(o)); + for (int i = 0; i < Array.getLength(o); i++) + l.add((T) Array.get(o, i)); + return l; + } + + @SuppressWarnings("unchecked") + public static T[] delist(List l, Class c) throws IllegalArgumentException { + return l.toArray((T[]) Array.newInstance(c, 0)); + } + + public static Object delistprimitive(List l, Class c) throws IllegalArgumentException { + Object o = Array.newInstance(c, l.size()); + for (int i = 0; i < l.size(); i++) + Array.set(o, i, l.get(i)); + return o; + } + + @SuppressWarnings("unchecked") + public static Object convert(Object o, Class c) throws IllegalArgumentException { + /* Possible Conversions: + * + ** List -> List + ** List -> int[] + ** List -> Integer[] + ** int[] -> int[] + ** int[] -> List + ** int[] -> Integer[] + ** Integer[] -> Integer[] + ** Integer[] -> int[] + ** Integer[] -> List + */ + try { + // List -> List + if (List.class.equals(c) + && o instanceof List) + return o; + + // int[] -> List + // Integer[] -> List + if (List.class.equals(c) + && o.getClass().isArray()) + return listify(o); + + // int[] -> int[] + // Integer[] -> Integer[] + if (o.getClass().isArray() + && c.isArray() + && o.getClass().getComponentType().equals(c.getComponentType())) + return o; + + // int[] -> Integer[] + if (o.getClass().isArray() + && c.isArray() + && o.getClass().getComponentType().isPrimitive()) + return wrap(o); + + // Integer[] -> int[] + if (o.getClass().isArray() + && c.isArray() + && c.getComponentType().isPrimitive()) + return unwrap((Object[]) o); + + // List -> int[] + if (o instanceof List + && c.isArray() + && c.getComponentType().isPrimitive()) + return delistprimitive((List) o, (Class) c.getComponentType()); + + // List -> Integer[] + if (o instanceof List + && c.isArray()) + return delist((List) o, (Class) c.getComponentType()); + + if (o.getClass().isArray() + && c.isArray()) + return type((Object[]) o, (Class) c.getComponentType()); + + } catch (Exception e) { + if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, e); + throw new IllegalArgumentException(e); + } + + throw new IllegalArgumentException(MessageFormat.format(getString("convertionTypeNotExpected"), new Object[]{o.getClass(), c})); + } + + public static Object[] type(Object[] old, Class c) { + Object[] ns = (Object[]) Array.newInstance(c, old.length); + for (int i = 0; i < ns.length; i++) + ns[i] = old[i]; + return ns; + } +} diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/BusAddress.java b/federation/sssd/src/main/java/org/freedesktop/dbus/BusAddress.java new file mode 100644 index 0000000000..3848951f39 --- /dev/null +++ b/federation/sssd/src/main/java/org/freedesktop/dbus/BusAddress.java @@ -0,0 +1,52 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; + +import cx.ath.matthew.debug.Debug; + +import java.text.ParseException; +import java.util.HashMap; +import java.util.Map; + +import static org.freedesktop.dbus.Gettext.getString; + +public class BusAddress { + private String type; + private Map parameters; + + public BusAddress(String address) throws ParseException { + if (null == address || "".equals(address)) throw new ParseException(getString("busAddressBlank"), 0); + if (Debug.debug) Debug.print(Debug.VERBOSE, "Parsing bus address: " + address); + String[] ss = address.split(":", 2); + if (ss.length < 2) throw new ParseException(getString("busAddressInvalid") + address, 0); + type = ss[0]; + if (Debug.debug) Debug.print(Debug.VERBOSE, "Transport type: " + type); + String[] ps = ss[1].split(","); + parameters = new HashMap(); + for (String p : ps) { + String[] kv = p.split("=", 2); + parameters.put(kv[0], kv[1]); + } + if (Debug.debug) Debug.print(Debug.VERBOSE, "Transport options: " + parameters); + } + + public String getType() { + return type; + } + + public String getParameter(String key) { + return parameters.get(key); + } + + public String toString() { + return type + ": " + parameters; + } +} diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/CallbackHandler.java b/federation/sssd/src/main/java/org/freedesktop/dbus/CallbackHandler.java new file mode 100644 index 0000000000..0e2a81e77a --- /dev/null +++ b/federation/sssd/src/main/java/org/freedesktop/dbus/CallbackHandler.java @@ -0,0 +1,22 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; + +import org.freedesktop.dbus.exceptions.DBusExecutionException; + +/** + * Interface for callbacks in async mode + */ +public interface CallbackHandler { + public void handle(ReturnType r); + + public void handleError(DBusExecutionException e); +} diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/Container.java b/federation/sssd/src/main/java/org/freedesktop/dbus/Container.java new file mode 100644 index 0000000000..39fd245527 --- /dev/null +++ b/federation/sssd/src/main/java/org/freedesktop/dbus/Container.java @@ -0,0 +1,93 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; + +import java.lang.reflect.Field; +import java.lang.reflect.Type; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +/** + * This class is the super class of both Structs and Tuples + * and holds common methods. + */ +abstract class Container { + private static Map typecache = new HashMap(); + + static void putTypeCache(Type k, Type[] v) { + typecache.put(k, v); + } + + static Type[] getTypeCache(Type k) { + return typecache.get(k); + } + + private Object[] parameters = null; + + public Container() { + } + + private void setup() { + Field[] fs = getClass().getDeclaredFields(); + Object[] args = new Object[fs.length]; + + int diff = 0; + for (Field f : fs) { + Position p = f.getAnnotation(Position.class); + if (null == p) { + diff++; + continue; + } + try { + args[p.value()] = f.get(this); + } catch (IllegalAccessException IAe) { + } + } + + this.parameters = new Object[args.length - diff]; + System.arraycopy(args, 0, parameters, 0, parameters.length); + } + + /** + * Returns the struct contents in order. + * + * @throws DBusException If there is a problem doing this. + */ + public final Object[] getParameters() { + if (null != parameters) return parameters; + setup(); + return parameters; + } + + /** + * Returns this struct as a string. + */ + public final String toString() { + String s = getClass().getName() + "<"; + if (null == parameters) + setup(); + if (0 == parameters.length) + return s + ">"; + for (Object o : parameters) + s += o + ", "; + return s.replaceAll(", $", ">"); + } + + public final boolean equals(Object other) { + if (other instanceof Container) { + Container that = (Container) other; + if (this.getClass().equals(that.getClass())) + return Arrays.equals(this.getParameters(), that.getParameters()); + else return false; + } else return false; + } +} diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/DBusAsyncReply.java b/federation/sssd/src/main/java/org/freedesktop/dbus/DBusAsyncReply.java new file mode 100644 index 0000000000..51c2bfd831 --- /dev/null +++ b/federation/sssd/src/main/java/org/freedesktop/dbus/DBusAsyncReply.java @@ -0,0 +1,117 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; + +import cx.ath.matthew.debug.Debug; +import org.freedesktop.DBus.Error.NoReply; +import org.freedesktop.dbus.exceptions.DBusException; +import org.freedesktop.dbus.exceptions.DBusExecutionException; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; + +import static org.freedesktop.dbus.Gettext.getString; + +/** + * A handle to an asynchronous method call. + */ +public class DBusAsyncReply { + /** + * Check if any of a set of asynchronous calls have had a reply. + * + * @param replies A Collection of handles to replies to check. + * @return A Collection only containing those calls which have had replies. + */ + public static Collection> hasReply(Collection> replies) { + Collection> c = new ArrayList>(replies); + Iterator> i = c.iterator(); + while (i.hasNext()) + if (!i.next().hasReply()) i.remove(); + return c; + } + + private ReturnType rval = null; + private DBusExecutionException error = null; + private MethodCall mc; + private Method me; + private AbstractConnection conn; + + DBusAsyncReply(MethodCall mc, Method me, AbstractConnection conn) { + this.mc = mc; + this.me = me; + this.conn = conn; + } + + @SuppressWarnings("unchecked") + private synchronized void checkReply() { + if (mc.hasReply()) { + Message m = mc.getReply(); + if (m instanceof Error) + error = ((Error) m).getException(); + else if (m instanceof MethodReturn) { + try { + rval = (ReturnType) RemoteInvocationHandler.convertRV(m.getSig(), m.getParameters(), me, conn); + } catch (DBusExecutionException DBEe) { + error = DBEe; + } catch (DBusException DBe) { + if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, DBe); + error = new DBusExecutionException(DBe.getMessage()); + } + } + } + } + + /** + * Check if we've had a reply. + * + * @return True if we have a reply + */ + public boolean hasReply() { + if (null != rval || null != error) return true; + checkReply(); + return null != rval || null != error; + } + + /** + * Get the reply. + * + * @return The return value from the method. + * @throws DBusExecutionException if the reply to the method was an error. + * @throws NoReply if the method hasn't had a reply yet + */ + public ReturnType getReply() throws DBusExecutionException { + if (null != rval) return rval; + else if (null != error) throw error; + checkReply(); + if (null != rval) return rval; + else if (null != error) throw error; + else throw new NoReply(getString("asyncCallNoReply")); + } + + public String toString() { + return getString("waitingFor") + mc; + } + + Method getMethod() { + return me; + } + + AbstractConnection getConnection() { + return conn; + } + + MethodCall getCall() { + return mc; + } +} + diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/DBusCallInfo.java b/federation/sssd/src/main/java/org/freedesktop/dbus/DBusCallInfo.java new file mode 100644 index 0000000000..1c9d143154 --- /dev/null +++ b/federation/sssd/src/main/java/org/freedesktop/dbus/DBusCallInfo.java @@ -0,0 +1,79 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; + +/** + * Holds information on a method call + */ +public class DBusCallInfo { + /** + * Indicates the caller won't wait for a reply (and we won't send one). + */ + public static final int NO_REPLY = Message.Flags.NO_REPLY_EXPECTED; + public static final int ASYNC = 0x100; + private String source; + private String destination; + private String objectpath; + private String iface; + private String method; + private int flags; + + DBusCallInfo(Message m) { + this.source = m.getSource(); + this.destination = m.getDestination(); + this.objectpath = m.getPath(); + this.iface = m.getInterface(); + this.method = m.getName(); + this.flags = m.getFlags(); + } + + /** + * Returns the BusID which called the method + */ + public String getSource() { + return source; + } + + /** + * Returns the name with which we were addressed on the Bus + */ + public String getDestination() { + return destination; + } + + /** + * Returns the object path used to call this method + */ + public String getObjectPath() { + return objectpath; + } + + /** + * Returns the interface this method was called with + */ + public String getInterface() { + return iface; + } + + /** + * Returns the method name used to call this method + */ + public String getMethod() { + return method; + } + + /** + * Returns any flags set on this method call + */ + public int getFlags() { + return flags; + } +} diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/DBusConnection.java b/federation/sssd/src/main/java/org/freedesktop/dbus/DBusConnection.java new file mode 100644 index 0000000000..45e33a4674 --- /dev/null +++ b/federation/sssd/src/main/java/org/freedesktop/dbus/DBusConnection.java @@ -0,0 +1,794 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; + +import cx.ath.matthew.debug.Debug; +import org.freedesktop.DBus; +import org.freedesktop.dbus.exceptions.DBusException; +import org.freedesktop.dbus.exceptions.DBusExecutionException; +import org.freedesktop.dbus.exceptions.NotConnected; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.lang.reflect.Proxy; +import java.text.MessageFormat; +import java.text.ParseException; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; +import java.util.Vector; + +import static org.freedesktop.dbus.Gettext.getString; + +/** + * Handles a connection to DBus. + *

+ * This is a Singleton class, only 1 connection to the SYSTEM or SESSION busses can be made. + * Repeated calls to getConnection will return the same reference. + *

+ *

+ * Signal Handlers and method calls from remote objects are run in their own threads, you MUST handle the concurrency issues. + *

+ */ +public class DBusConnection extends AbstractConnection { + /** + * Add addresses of peers to a set which will watch for them to + * disappear and automatically remove them from the set. + */ + public class PeerSet implements Set, DBusSigHandler { + private Set addresses; + + public PeerSet() { + addresses = new TreeSet(); + try { + addSigHandler(new DBusMatchRule(DBus.NameOwnerChanged.class, null, null), this); + } catch (DBusException DBe) { + if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, DBe); + } + } + + public void handle(DBus.NameOwnerChanged noc) { + if (Debug.debug) + Debug.print(Debug.DEBUG, "Received NameOwnerChanged(" + noc.name + "," + noc.old_owner + "," + noc.new_owner + ")"); + if ("".equals(noc.new_owner) && addresses.contains(noc.name)) + remove(noc.name); + } + + public boolean add(String address) { + if (Debug.debug) + Debug.print(Debug.DEBUG, "Adding " + address); + synchronized (addresses) { + return addresses.add(address); + } + } + + public boolean addAll(Collection addresses) { + synchronized (this.addresses) { + return this.addresses.addAll(addresses); + } + } + + public void clear() { + synchronized (addresses) { + addresses.clear(); + } + } + + public boolean contains(Object o) { + return addresses.contains(o); + } + + public boolean containsAll(Collection os) { + return addresses.containsAll(os); + } + + public boolean equals(Object o) { + if (o instanceof PeerSet) + return ((PeerSet) o).addresses.equals(addresses); + else return false; + } + + public int hashCode() { + return addresses.hashCode(); + } + + public boolean isEmpty() { + return addresses.isEmpty(); + } + + public Iterator iterator() { + return addresses.iterator(); + } + + public boolean remove(Object o) { + if (Debug.debug) + Debug.print(Debug.DEBUG, "Removing " + o); + synchronized (addresses) { + return addresses.remove(o); + } + } + + public boolean removeAll(Collection os) { + synchronized (addresses) { + return addresses.removeAll(os); + } + } + + public boolean retainAll(Collection os) { + synchronized (addresses) { + return addresses.retainAll(os); + } + } + + public int size() { + return addresses.size(); + } + + public Object[] toArray() { + synchronized (addresses) { + return addresses.toArray(); + } + } + + public T[] toArray(T[] a) { + synchronized (addresses) { + return addresses.toArray(a); + } + } + } + + private class _sighandler implements DBusSigHandler { + public void handle(DBusSignal s) { + if (s instanceof org.freedesktop.DBus.Local.Disconnected) { + if (Debug.debug) Debug.print(Debug.WARN, "Handling disconnected signal from bus"); + try { + Error err = new Error( + "org.freedesktop.DBus.Local", "org.freedesktop.DBus.Local.disconnected", 0, "s", new Object[]{getString("disconnected")}); + if (null != pendingCalls) synchronized (pendingCalls) { + long[] set = pendingCalls.getKeys(); + for (long l : set) + if (-1 != l) { + MethodCall m = pendingCalls.remove(l); + if (null != m) + m.setReply(err); + } + } + synchronized (pendingErrors) { + pendingErrors.add(err); + } + } catch (DBusException DBe) { + } + } else if (s instanceof org.freedesktop.DBus.NameAcquired) { + busnames.add(((org.freedesktop.DBus.NameAcquired) s).name); + } + } + } + + /** + * System Bus + */ + public static final int SYSTEM = 0; + /** + * Session Bus + */ + public static final int SESSION = 1; + + public static final String DEFAULT_SYSTEM_BUS_ADDRESS = "unix:path=/var/run/dbus/system_bus_socket"; + + private List busnames; + + private static final Map conn = new HashMap(); + private int _refcount = 0; + private Object _reflock = new Object(); + private DBus _dbus; + + /** + * Connect to the BUS. If a connection already exists to the specified Bus, a reference to it is returned. + * + * @param address The address of the bus to connect to + * @throws DBusException If there is a problem connecting to the Bus. + */ + public static DBusConnection getConnection(String address) throws DBusException { + synchronized (conn) { + DBusConnection c = conn.get(address); + if (null != c) { + synchronized (c._reflock) { + c._refcount++; + } + return c; + } else { + c = new DBusConnection(address); + conn.put(address, c); + return c; + } + } + } + + /** + * Connect to the BUS. If a connection already exists to the specified Bus, a reference to it is returned. + * + * @param bustype The Bus to connect to. + * @throws DBusException If there is a problem connecting to the Bus. + * @see #SYSTEM + * @see #SESSION + */ + public static DBusConnection getConnection(int bustype) throws DBusException { + synchronized (conn) { + String s = null; + switch (bustype) { + case SYSTEM: + s = System.getenv("DBUS_SYSTEM_BUS_ADDRESS"); + if (null == s) s = DEFAULT_SYSTEM_BUS_ADDRESS; + break; + case SESSION: + s = System.getenv("DBUS_SESSION_BUS_ADDRESS"); + if (null == s) { + // address gets stashed in $HOME/.dbus/session-bus/`dbus-uuidgen --get`-`sed 's/:\(.\)\..*/\1/' <<< $DISPLAY` + String display = System.getenv("DISPLAY"); + if (null == display) throw new DBusException(getString("cannotResolveSessionBusAddress")); + File uuidfile = new File("/var/lib/dbus/machine-id"); + if (!uuidfile.exists()) throw new DBusException(getString("cannotResolveSessionBusAddress")); + try { + BufferedReader r = new BufferedReader(new FileReader(uuidfile)); + String uuid = r.readLine(); + String homedir = System.getProperty("user.home"); + File addressfile = new File(homedir + "/.dbus/session-bus", + uuid + "-" + display.replaceAll(":([0-9]*)\\..*", "$1")); + if (!addressfile.exists()) + throw new DBusException(getString("cannotResolveSessionBusAddress")); + r = new BufferedReader(new FileReader(addressfile)); + String l; + while (null != (l = r.readLine())) { + if (Debug.debug) Debug.print(Debug.VERBOSE, "Reading D-Bus session data: " + l); + if (l.matches("DBUS_SESSION_BUS_ADDRESS.*")) { + s = l.replaceAll("^[^=]*=", ""); + if (Debug.debug) Debug.print(Debug.VERBOSE, "Parsing " + l + " to " + s); + } + } + if (null == s || "".equals(s)) + throw new DBusException(getString("cannotResolveSessionBusAddress")); + if (Debug.debug) + Debug.print(Debug.INFO, "Read bus address " + s + " from file " + addressfile.toString()); + } catch (Exception e) { + if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, e); + throw new DBusException(getString("cannotResolveSessionBusAddress")); + } + } + break; + default: + throw new DBusException(getString("invalidBusType") + bustype); + } + DBusConnection c = conn.get(s); + if (Debug.debug) Debug.print(Debug.VERBOSE, "Getting bus connection for " + s + ": " + c); + if (null != c) { + synchronized (c._reflock) { + c._refcount++; + } + return c; + } else { + if (Debug.debug) Debug.print(Debug.DEBUG, "Creating new bus connection to: " + s); + c = new DBusConnection(s); + conn.put(s, c); + return c; + } + } + } + + @SuppressWarnings("unchecked") + private DBusConnection(String address) throws DBusException { + super(address); + busnames = new Vector(); + + synchronized (_reflock) { + _refcount = 1; + } + + try { + transport = new Transport(addr, AbstractConnection.TIMEOUT); + connected = true; + } catch (IOException IOe) { + if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, IOe); + disconnect(); + throw new DBusException(getString("connectionFailure") + IOe.getMessage()); + } catch (ParseException Pe) { + if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, Pe); + disconnect(); + throw new DBusException(getString("connectionFailure") + Pe.getMessage()); + } + + // start listening for calls + listen(); + + // register disconnect handlers + DBusSigHandler h = new _sighandler(); + addSigHandlerWithoutMatch(org.freedesktop.DBus.Local.Disconnected.class, h); + addSigHandlerWithoutMatch(org.freedesktop.DBus.NameAcquired.class, h); + + // register ourselves + _dbus = getRemoteObject("org.freedesktop.DBus", "/org/freedesktop/DBus", DBus.class); + try { + busnames.add(_dbus.Hello()); + } catch (DBusExecutionException DBEe) { + if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, DBEe); + throw new DBusException(DBEe.getMessage()); + } + } + + @SuppressWarnings("unchecked") + DBusInterface dynamicProxy(String source, String path) throws DBusException { + if (Debug.debug) + Debug.print(Debug.INFO, "Introspecting " + path + " on " + source + " for dynamic proxy creation"); + try { + DBus.Introspectable intro = getRemoteObject(source, path, DBus.Introspectable.class); + String data = intro.Introspect(); + if (Debug.debug) Debug.print(Debug.VERBOSE, "Got introspection data: " + data); + String[] tags = data.split("[<>]"); + Vector ifaces = new Vector(); + for (String tag : tags) { + if (tag.startsWith("interface")) { + ifaces.add(tag.replaceAll("^interface *name *= *['\"]([^'\"]*)['\"].*$", "$1")); + } + } + Vector> ifcs = new Vector>(); + for (String iface : ifaces) { + if (Debug.debug) Debug.print(Debug.DEBUG, "Trying interface " + iface); + int j = 0; + while (j >= 0) { + try { + Class ifclass = Class.forName(iface); + if (!ifcs.contains(ifclass)) + ifcs.add(ifclass); + break; + } catch (Exception e) { + } + j = iface.lastIndexOf("."); + char[] cs = iface.toCharArray(); + if (j >= 0) { + cs[j] = '$'; + iface = String.valueOf(cs); + } + } + } + + if (ifcs.size() == 0) throw new DBusException(getString("interfaceToCastNotFound")); + + RemoteObject ro = new RemoteObject(source, path, null, false); + DBusInterface newi = (DBusInterface) + Proxy.newProxyInstance(ifcs.get(0).getClassLoader(), + ifcs.toArray(new Class[0]), + new RemoteInvocationHandler(this, ro)); + importedObjects.put(newi, ro); + return newi; + } catch (Exception e) { + if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, e); + throw new DBusException(MessageFormat.format(getString("createProxyExportFailure"), new Object[]{path, source, e.getMessage()})); + } + } + + DBusInterface getExportedObject(String source, String path) throws DBusException { + ExportedObject o = null; + synchronized (exportedObjects) { + o = exportedObjects.get(path); + } + if (null != o && null == o.object.get()) { + unExportObject(path); + o = null; + } + if (null != o) return o.object.get(); + if (null == source) throw new DBusException(getString("objectNotExportedNoRemoteSpecified")); + return dynamicProxy(source, path); + } + + /** + * Release a bus name. + * Releases the name so that other people can use it + * + * @param busname The name to release. MUST be in dot-notation like "org.freedesktop.local" + * @throws DBusException If the busname is incorrectly formatted. + */ + public void releaseBusName(String busname) throws DBusException { + if (!busname.matches(BUSNAME_REGEX) || busname.length() > MAX_NAME_LENGTH) + throw new DBusException(getString("invalidBusName")); + synchronized (this.busnames) { + UInt32 rv; + try { + rv = _dbus.ReleaseName(busname); + } catch (DBusExecutionException DBEe) { + if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, DBEe); + throw new DBusException(DBEe.getMessage()); + } + this.busnames.remove(busname); + } + } + + /** + * Request a bus name. + * Request the well known name that this should respond to on the Bus. + * + * @param busname The name to respond to. MUST be in dot-notation like "org.freedesktop.local" + * @throws DBusException If the register name failed, or our name already exists on the bus. + * or if busname is incorrectly formatted. + */ + public void requestBusName(String busname) throws DBusException { + if (!busname.matches(BUSNAME_REGEX) || busname.length() > MAX_NAME_LENGTH) + throw new DBusException(getString("invalidBusName")); + synchronized (this.busnames) { + UInt32 rv; + try { + rv = _dbus.RequestName(busname, + new UInt32(DBus.DBUS_NAME_FLAG_REPLACE_EXISTING | + DBus.DBUS_NAME_FLAG_DO_NOT_QUEUE)); + } catch (DBusExecutionException DBEe) { + if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, DBEe); + throw new DBusException(DBEe.getMessage()); + } + switch (rv.intValue()) { + case DBus.DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER: + break; + case DBus.DBUS_REQUEST_NAME_REPLY_IN_QUEUE: + throw new DBusException(getString("dbusRegistrationFailure")); + case DBus.DBUS_REQUEST_NAME_REPLY_EXISTS: + throw new DBusException(getString("dbusRegistrationFailure")); + case DBus.DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER: + break; + default: + break; + } + this.busnames.add(busname); + } + } + + /** + * Returns the unique name of this connection. + */ + public String getUniqueName() { + return busnames.get(0); + } + + /** + * Returns all the names owned by this connection. + */ + public String[] getNames() { + Set names = new TreeSet(); + names.addAll(busnames); + return names.toArray(new String[0]); + } + + public I getPeerRemoteObject(String busname, String objectpath, Class type) throws DBusException { + return getPeerRemoteObject(busname, objectpath, type, true); + } + + /** + * Return a reference to a remote object. + * This method will resolve the well known name (if given) to a unique bus name when you call it. + * This means that if a well known name is released by one process and acquired by another calls to + * objects gained from this method will continue to operate on the original process. + *

+ * This method will use bus introspection to determine the interfaces on a remote object and so + * may block and may fail. The resulting proxy object will, however, be castable + * to any interface it implements. It will also autostart the process if applicable. Also note + * that the resulting proxy may fail to execute the correct method with overloaded methods + * and that complex types may fail in interesting ways. Basically, if something odd happens, + * try specifying the interface explicitly. + * + * @param busname The bus name to connect to. Usually a well known bus name in dot-notation (such as "org.freedesktop.local") + * or may be a DBus address such as ":1-16". + * @param objectpath The path on which the process is exporting the object.$ + * @return A reference to a remote object. + * @throws ClassCastException If type is not a sub-type of DBusInterface + * @throws DBusException If busname or objectpath are incorrectly formatted. + */ + public DBusInterface getPeerRemoteObject(String busname, String objectpath) throws DBusException { + if (null == busname) throw new DBusException(getString("nullBusName")); + + if ((!busname.matches(BUSNAME_REGEX) && !busname.matches(CONNID_REGEX)) + || busname.length() > MAX_NAME_LENGTH) + throw new DBusException(getString("invalidBusName") + busname); + + String unique = _dbus.GetNameOwner(busname); + + return dynamicProxy(unique, objectpath); + } + + /** + * Return a reference to a remote object. + * This method will always refer to the well known name (if given) rather than resolving it to a unique bus name. + * In particular this means that if a process providing the well known name disappears and is taken over by another process + * proxy objects gained by this method will make calls on the new proccess. + *

+ * This method will use bus introspection to determine the interfaces on a remote object and so + * may block and may fail. The resulting proxy object will, however, be castable + * to any interface it implements. It will also autostart the process if applicable. Also note + * that the resulting proxy may fail to execute the correct method with overloaded methods + * and that complex types may fail in interesting ways. Basically, if something odd happens, + * try specifying the interface explicitly. + * + * @param busname The bus name to connect to. Usually a well known bus name name in dot-notation (such as "org.freedesktop.local") + * or may be a DBus address such as ":1-16". + * @param objectpath The path on which the process is exporting the object. + * @return A reference to a remote object. + * @throws ClassCastException If type is not a sub-type of DBusInterface + * @throws DBusException If busname or objectpath are incorrectly formatted. + */ + public DBusInterface getRemoteObject(String busname, String objectpath) throws DBusException { + if (null == busname) throw new DBusException(getString("nullBusName")); + if (null == objectpath) throw new DBusException(getString("nullObjectPath")); + + if ((!busname.matches(BUSNAME_REGEX) && !busname.matches(CONNID_REGEX)) + || busname.length() > MAX_NAME_LENGTH) + throw new DBusException(getString("invalidBusName") + busname); + + if (!objectpath.matches(OBJECT_REGEX) || objectpath.length() > MAX_NAME_LENGTH) + throw new DBusException(getString("invalidObjectPath") + objectpath); + + return dynamicProxy(busname, objectpath); + } + + /** + * Return a reference to a remote object. + * This method will resolve the well known name (if given) to a unique bus name when you call it. + * This means that if a well known name is released by one process and acquired by another calls to + * objects gained from this method will continue to operate on the original process. + * + * @param busname The bus name to connect to. Usually a well known bus name in dot-notation (such as "org.freedesktop.local") + * or may be a DBus address such as ":1-16". + * @param objectpath The path on which the process is exporting the object.$ + * @param type The interface they are exporting it on. This type must have the same full class name and exposed method signatures + * as the interface the remote object is exporting. + * @param autostart Disable/Enable auto-starting of services in response to calls on this object. + * Default is enabled; when calling a method with auto-start enabled, if the destination is a well-known name + * and is not owned the bus will attempt to start a process to take the name. When disabled an error is + * returned immediately. + * @return A reference to a remote object. + * @throws ClassCastException If type is not a sub-type of DBusInterface + * @throws DBusException If busname or objectpath are incorrectly formatted or type is not in a package. + */ + public I getPeerRemoteObject(String busname, String objectpath, Class type, boolean autostart) throws DBusException { + if (null == busname) throw new DBusException(getString("nullBusName")); + + if ((!busname.matches(BUSNAME_REGEX) && !busname.matches(CONNID_REGEX)) + || busname.length() > MAX_NAME_LENGTH) + throw new DBusException(getString("invalidBusName") + busname); + + String unique = _dbus.GetNameOwner(busname); + + return getRemoteObject(unique, objectpath, type, autostart); + } + + /** + * Return a reference to a remote object. + * This method will always refer to the well known name (if given) rather than resolving it to a unique bus name. + * In particular this means that if a process providing the well known name disappears and is taken over by another process + * proxy objects gained by this method will make calls on the new proccess. + * + * @param busname The bus name to connect to. Usually a well known bus name name in dot-notation (such as "org.freedesktop.local") + * or may be a DBus address such as ":1-16". + * @param objectpath The path on which the process is exporting the object. + * @param type The interface they are exporting it on. This type must have the same full class name and exposed method signatures + * as the interface the remote object is exporting. + * @return A reference to a remote object. + * @throws ClassCastException If type is not a sub-type of DBusInterface + * @throws DBusException If busname or objectpath are incorrectly formatted or type is not in a package. + */ + public I getRemoteObject(String busname, String objectpath, Class type) throws DBusException { + return getRemoteObject(busname, objectpath, type, true); + } + + /** + * Return a reference to a remote object. + * This method will always refer to the well known name (if given) rather than resolving it to a unique bus name. + * In particular this means that if a process providing the well known name disappears and is taken over by another process + * proxy objects gained by this method will make calls on the new proccess. + * + * @param busname The bus name to connect to. Usually a well known bus name name in dot-notation (such as "org.freedesktop.local") + * or may be a DBus address such as ":1-16". + * @param objectpath The path on which the process is exporting the object. + * @param type The interface they are exporting it on. This type must have the same full class name and exposed method signatures + * as the interface the remote object is exporting. + * @param autostart Disable/Enable auto-starting of services in response to calls on this object. + * Default is enabled; when calling a method with auto-start enabled, if the destination is a well-known name + * and is not owned the bus will attempt to start a process to take the name. When disabled an error is + * returned immediately. + * @return A reference to a remote object. + * @throws ClassCastException If type is not a sub-type of DBusInterface + * @throws DBusException If busname or objectpath are incorrectly formatted or type is not in a package. + */ + @SuppressWarnings("unchecked") + public I getRemoteObject(String busname, String objectpath, Class type, boolean autostart) throws DBusException { + if (null == busname) throw new DBusException(getString("nullBusName")); + if (null == objectpath) throw new DBusException(getString("nullObjectPath")); + if (null == type) throw new ClassCastException(getString("notDBusInterface")); + + if ((!busname.matches(BUSNAME_REGEX) && !busname.matches(CONNID_REGEX)) + || busname.length() > MAX_NAME_LENGTH) + throw new DBusException(getString("invalidBusName") + busname); + + if (!objectpath.matches(OBJECT_REGEX) || objectpath.length() > MAX_NAME_LENGTH) + throw new DBusException(getString("invalidObjectPath") + objectpath); + + if (!DBusInterface.class.isAssignableFrom(type)) throw new ClassCastException(getString("notDBusInterface")); + + // don't let people import things which don't have a + // valid D-Bus interface name + if (type.getName().equals(type.getSimpleName())) + throw new DBusException(getString("interfaceNotAllowedOutsidePackage")); + + RemoteObject ro = new RemoteObject(busname, objectpath, type, autostart); + I i = (I) Proxy.newProxyInstance(type.getClassLoader(), + new Class[]{type}, new RemoteInvocationHandler(this, ro)); + importedObjects.put(i, ro); + return i; + } + + /** + * Remove a Signal Handler. + * Stops listening for this signal. + * + * @param type The signal to watch for. + * @param source The source of the signal. + * @throws DBusException If listening for the signal on the bus failed. + * @throws ClassCastException If type is not a sub-type of DBusSignal. + */ + public void removeSigHandler(Class type, String source, DBusSigHandler handler) throws DBusException { + if (!DBusSignal.class.isAssignableFrom(type)) throw new ClassCastException(getString("notDBusSignal")); + if (source.matches(BUSNAME_REGEX)) throw new DBusException(getString("cannotWatchSignalsWellKnownBussName")); + if (!source.matches(CONNID_REGEX) || source.length() > MAX_NAME_LENGTH) + throw new DBusException(getString("invalidBusName") + source); + removeSigHandler(new DBusMatchRule(type, source, null), handler); + } + + /** + * Remove a Signal Handler. + * Stops listening for this signal. + * + * @param type The signal to watch for. + * @param source The source of the signal. + * @param object The object emitting the signal. + * @throws DBusException If listening for the signal on the bus failed. + * @throws ClassCastException If type is not a sub-type of DBusSignal. + */ + public void removeSigHandler(Class type, String source, DBusInterface object, DBusSigHandler handler) throws DBusException { + if (!DBusSignal.class.isAssignableFrom(type)) throw new ClassCastException(getString("notDBusSignal")); + if (source.matches(BUSNAME_REGEX)) throw new DBusException(getString("cannotWatchSignalsWellKnownBussName")); + if (!source.matches(CONNID_REGEX) || source.length() > MAX_NAME_LENGTH) + throw new DBusException(getString("invalidBusName") + source); + String objectpath = importedObjects.get(object).objectpath; + if (!objectpath.matches(OBJECT_REGEX) || objectpath.length() > MAX_NAME_LENGTH) + throw new DBusException(getString("invalidObjectPath") + objectpath); + removeSigHandler(new DBusMatchRule(type, source, objectpath), handler); + } + + protected void removeSigHandler(DBusMatchRule rule, DBusSigHandler handler) throws DBusException { + + SignalTuple key = new SignalTuple(rule.getInterface(), rule.getMember(), rule.getObject(), rule.getSource()); + synchronized (handledSignals) { + Vector> v = handledSignals.get(key); + if (null != v) { + v.remove(handler); + if (0 == v.size()) { + handledSignals.remove(key); + try { + _dbus.RemoveMatch(rule.toString()); + } catch (NotConnected NC) { + if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, NC); + } catch (DBusExecutionException DBEe) { + if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, DBEe); + throw new DBusException(DBEe.getMessage()); + } + } + } + } + } + + /** + * Add a Signal Handler. + * Adds a signal handler to call when a signal is received which matches the specified type, name and source. + * + * @param type The signal to watch for. + * @param source The process which will send the signal. This MUST be a unique bus name and not a well known name. + * @param handler The handler to call when a signal is received. + * @throws DBusException If listening for the signal on the bus failed. + * @throws ClassCastException If type is not a sub-type of DBusSignal. + */ + @SuppressWarnings("unchecked") + public void addSigHandler(Class type, String source, DBusSigHandler handler) throws DBusException { + if (!DBusSignal.class.isAssignableFrom(type)) throw new ClassCastException(getString("notDBusSignal")); + if (source.matches(BUSNAME_REGEX)) throw new DBusException(getString("cannotWatchSignalsWellKnownBussName")); + if (!source.matches(CONNID_REGEX) || source.length() > MAX_NAME_LENGTH) + throw new DBusException(getString("invalidBusName") + source); + addSigHandler(new DBusMatchRule(type, source, null), (DBusSigHandler) handler); + } + + /** + * Add a Signal Handler. + * Adds a signal handler to call when a signal is received which matches the specified type, name, source and object. + * + * @param type The signal to watch for. + * @param source The process which will send the signal. This MUST be a unique bus name and not a well known name. + * @param object The object from which the signal will be emitted + * @param handler The handler to call when a signal is received. + * @throws DBusException If listening for the signal on the bus failed. + * @throws ClassCastException If type is not a sub-type of DBusSignal. + */ + @SuppressWarnings("unchecked") + public void addSigHandler(Class type, String source, DBusInterface object, DBusSigHandler handler) throws DBusException { + if (!DBusSignal.class.isAssignableFrom(type)) throw new ClassCastException(getString("notDBusSignal")); + if (source.matches(BUSNAME_REGEX)) throw new DBusException(getString("cannotWatchSignalsWellKnownBussName")); + if (!source.matches(CONNID_REGEX) || source.length() > MAX_NAME_LENGTH) + throw new DBusException(getString("invalidBusName") + source); + String objectpath = importedObjects.get(object).objectpath; + if (!objectpath.matches(OBJECT_REGEX) || objectpath.length() > MAX_NAME_LENGTH) + throw new DBusException(getString("invalidObjectPath") + objectpath); + addSigHandler(new DBusMatchRule(type, source, objectpath), (DBusSigHandler) handler); + } + + protected void addSigHandler(DBusMatchRule rule, DBusSigHandler handler) throws DBusException { + try { + _dbus.AddMatch(rule.toString()); + } catch (DBusExecutionException DBEe) { + if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, DBEe); + throw new DBusException(DBEe.getMessage()); + } + SignalTuple key = new SignalTuple(rule.getInterface(), rule.getMember(), rule.getObject(), rule.getSource()); + synchronized (handledSignals) { + Vector> v = handledSignals.get(key); + if (null == v) { + v = new Vector>(); + v.add(handler); + handledSignals.put(key, v); + } else + v.add(handler); + } + } + + /** + * Disconnect from the Bus. + * This only disconnects when the last reference to the bus has disconnect called on it + * or has been destroyed. + */ + public void disconnect() { + synchronized (conn) { + synchronized (_reflock) { + if (0 == --_refcount) { + if (Debug.debug) Debug.print(Debug.INFO, "Disconnecting DBusConnection"); + // Set all pending messages to have an error. + try { + Error err = new Error( + "org.freedesktop.DBus.Local", "org.freedesktop.DBus.Local.disconnected", 0, "s", new Object[]{getString("disconnected")}); + synchronized (pendingCalls) { + long[] set = pendingCalls.getKeys(); + for (long l : set) + if (-1 != l) { + MethodCall m = pendingCalls.remove(l); + if (null != m) + m.setReply(err); + } + pendingCalls = null; + } + synchronized (pendingErrors) { + pendingErrors.add(err); + } + } catch (DBusException DBe) { + } + + conn.remove(addr); + super.disconnect(); + } + } + } + } +} diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/DBusInterface.java b/federation/sssd/src/main/java/org/freedesktop/dbus/DBusInterface.java new file mode 100644 index 0000000000..b9e404c100 --- /dev/null +++ b/federation/sssd/src/main/java/org/freedesktop/dbus/DBusInterface.java @@ -0,0 +1,31 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; + +/** + * Denotes a class as exportable or a remote interface which can be called. + *

+ * Any interface which should be exported or imported should extend this + * interface. All public methods from that interface are exported/imported + * with the given method signatures. + *

+ *

+ * All method calls on exported objects are run in their own threads. + * Application writers are responsible for any concurrency issues. + *

+ */ +public interface DBusInterface { + /** + * Returns true on remote objects. + * Local objects implementing this interface MUST return false. + */ + public boolean isRemote(); +} diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/DBusInterfaceName.java b/federation/sssd/src/main/java/org/freedesktop/dbus/DBusInterfaceName.java new file mode 100644 index 0000000000..0fc705685a --- /dev/null +++ b/federation/sssd/src/main/java/org/freedesktop/dbus/DBusInterfaceName.java @@ -0,0 +1,28 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Force the interface name to be different to the Java class name. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface DBusInterfaceName { + /** + * The replacement interface name. + */ + String value(); +} diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/DBusMap.java b/federation/sssd/src/main/java/org/freedesktop/dbus/DBusMap.java new file mode 100644 index 0000000000..6590e62448 --- /dev/null +++ b/federation/sssd/src/main/java/org/freedesktop/dbus/DBusMap.java @@ -0,0 +1,150 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; + +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; +import java.util.Vector; + +class DBusMap implements Map { + Object[][] entries; + + public DBusMap(Object[][] entries) { + this.entries = entries; + } + + class Entry implements Map.Entry, Comparable { + private int entry; + + public Entry(int i) { + this.entry = i; + } + + public boolean equals(Object o) { + if (null == o) return false; + if (!(o instanceof DBusMap.Entry)) return false; + return this.entry == ((Entry) o).entry; + } + + @SuppressWarnings("unchecked") + public K getKey() { + return (K) entries[entry][0]; + } + + @SuppressWarnings("unchecked") + public V getValue() { + return (V) entries[entry][1]; + } + + public int hashCode() { + return entries[entry][0].hashCode(); + } + + public V setValue(V value) { + throw new UnsupportedOperationException(); + } + + public int compareTo(Entry e) { + return entry - e.entry; + } + } + + public void clear() { + throw new UnsupportedOperationException(); + } + + public boolean containsKey(Object key) { + for (int i = 0; i < entries.length; i++) + if (key == entries[i][0] || (key != null && key.equals(entries[i][0]))) + return true; + return false; + } + + public boolean containsValue(Object value) { + for (int i = 0; i < entries.length; i++) + if (value == entries[i][1] || (value != null && value.equals(entries[i][1]))) + return true; + return false; + } + + public Set> entrySet() { + Set> s = new TreeSet>(); + for (int i = 0; i < entries.length; i++) + s.add(new Entry(i)); + return s; + } + + @SuppressWarnings("unchecked") + public V get(Object key) { + for (int i = 0; i < entries.length; i++) + if (key == entries[i][0] || (key != null && key.equals(entries[i][0]))) + return (V) entries[i][1]; + return null; + } + + public boolean isEmpty() { + return entries.length == 0; + } + + @SuppressWarnings("unchecked") + public Set keySet() { + Set s = new TreeSet(); + for (Object[] entry : entries) + s.add((K) entry[0]); + return s; + } + + public V put(K key, V value) { + throw new UnsupportedOperationException(); + } + + public void putAll(Map t) { + throw new UnsupportedOperationException(); + } + + public V remove(Object key) { + throw new UnsupportedOperationException(); + } + + public int size() { + return entries.length; + } + + @SuppressWarnings("unchecked") + public Collection values() { + List l = new Vector(); + for (Object[] entry : entries) + l.add((V) entry[1]); + return l; + } + + public int hashCode() { + return Arrays.deepHashCode(entries); + } + + @SuppressWarnings("unchecked") + public boolean equals(Object o) { + if (null == o) return false; + if (!(o instanceof Map)) return false; + return ((Map) o).entrySet().equals(entrySet()); + } + + public String toString() { + String s = "{ "; + for (int i = 0; i < entries.length; i++) + s += entries[i][0] + " => " + entries[i][1] + ","; + return s.replaceAll(".$", " }"); + } +} diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/DBusMatchRule.java b/federation/sssd/src/main/java/org/freedesktop/dbus/DBusMatchRule.java new file mode 100644 index 0000000000..fa886c0cf9 --- /dev/null +++ b/federation/sssd/src/main/java/org/freedesktop/dbus/DBusMatchRule.java @@ -0,0 +1,151 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; + +import org.freedesktop.dbus.exceptions.DBusException; +import org.freedesktop.dbus.exceptions.DBusExecutionException; + +import java.util.HashMap; + +import static org.freedesktop.dbus.Gettext.getString; + +public class DBusMatchRule { + /* signal, error, method_call, method_reply */ + private String type; + private String iface; + private String member; + private String object; + private String source; + private static HashMap> signalTypeMap = + new HashMap>(); + + static Class getCachedSignalType(String type) { + return signalTypeMap.get(type); + } + + public DBusMatchRule(String type, String iface, String member) { + this.type = type; + this.iface = iface; + this.member = member; + } + + public DBusMatchRule(DBusExecutionException e) throws DBusException { + this(e.getClass()); + member = null; + type = "error"; + } + + public DBusMatchRule(Message m) { + iface = m.getInterface(); + member = m.getName(); + if (m instanceof DBusSignal) + type = "signal"; + else if (m instanceof Error) { + type = "error"; + member = null; + } else if (m instanceof MethodCall) + type = "method_call"; + else if (m instanceof MethodReturn) + type = "method_reply"; + } + + public DBusMatchRule(Class c, String method) throws DBusException { + this(c); + member = method; + type = "method_call"; + } + + public DBusMatchRule(Class c, String source, String object) throws DBusException { + this(c); + this.source = source; + this.object = object; + } + + @SuppressWarnings("unchecked") + public DBusMatchRule(Class c) throws DBusException { + if (DBusInterface.class.isAssignableFrom(c)) { + if (null != c.getAnnotation(DBusInterfaceName.class)) + iface = c.getAnnotation(DBusInterfaceName.class).value(); + else + iface = AbstractConnection.dollar_pattern.matcher(c.getName()).replaceAll("."); + if (!iface.matches(".*\\..*")) + throw new DBusException(getString("interfaceMustBeDefinedPackage")); + member = null; + type = null; + } else if (DBusSignal.class.isAssignableFrom(c)) { + if (null == c.getEnclosingClass()) + throw new DBusException(getString("signalsMustBeMemberOfClass")); + else if (null != c.getEnclosingClass().getAnnotation(DBusInterfaceName.class)) + iface = c.getEnclosingClass().getAnnotation(DBusInterfaceName.class).value(); + else + iface = AbstractConnection.dollar_pattern.matcher(c.getEnclosingClass().getName()).replaceAll("."); + // Don't export things which are invalid D-Bus interfaces + if (!iface.matches(".*\\..*")) + throw new DBusException(getString("interfaceMustBeDefinedPackage")); + if (c.isAnnotationPresent(DBusMemberName.class)) + member = c.getAnnotation(DBusMemberName.class).value(); + else + member = c.getSimpleName(); + signalTypeMap.put(iface + '$' + member, (Class) c); + type = "signal"; + } else if (Error.class.isAssignableFrom(c)) { + if (null != c.getAnnotation(DBusInterfaceName.class)) + iface = c.getAnnotation(DBusInterfaceName.class).value(); + else + iface = AbstractConnection.dollar_pattern.matcher(c.getName()).replaceAll("."); + if (!iface.matches(".*\\..*")) + throw new DBusException(getString("interfaceMustBeDefinedPackage")); + member = null; + type = "error"; + } else if (DBusExecutionException.class.isAssignableFrom(c)) { + if (null != c.getClass().getAnnotation(DBusInterfaceName.class)) + iface = c.getClass().getAnnotation(DBusInterfaceName.class).value(); + else + iface = AbstractConnection.dollar_pattern.matcher(c.getClass().getName()).replaceAll("."); + if (!iface.matches(".*\\..*")) + throw new DBusException(getString("interfaceMustBeDefinedPackage")); + member = null; + type = "error"; + } else + throw new DBusException(getString("invalidTypeMatchRule") + c); + } + + public String toString() { + String s = null; + if (null != type) s = null == s ? "type='" + type + "'" : s + ",type='" + type + "'"; + if (null != member) s = null == s ? "member='" + member + "'" : s + ",member='" + member + "'"; + if (null != iface) s = null == s ? "interface='" + iface + "'" : s + ",interface='" + iface + "'"; + if (null != source) s = null == s ? "sender='" + source + "'" : s + ",sender='" + source + "'"; + if (null != object) s = null == s ? "path='" + object + "'" : s + ",path='" + object + "'"; + return s; + } + + public String getType() { + return type; + } + + public String getInterface() { + return iface; + } + + public String getMember() { + return member; + } + + public String getSource() { + return source; + } + + public String getObject() { + return object; + } + +} diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/DBusMemberName.java b/federation/sssd/src/main/java/org/freedesktop/dbus/DBusMemberName.java new file mode 100644 index 0000000000..25d30d7e37 --- /dev/null +++ b/federation/sssd/src/main/java/org/freedesktop/dbus/DBusMemberName.java @@ -0,0 +1,29 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Force the member (method/signal) name on the bus to be different to the Java name. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE, ElementType.METHOD}) +public @interface DBusMemberName { + /** + * The replacement member name. + */ + String value(); +} + diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/DBusSerializable.java b/federation/sssd/src/main/java/org/freedesktop/dbus/DBusSerializable.java new file mode 100644 index 0000000000..8d9fea58ae --- /dev/null +++ b/federation/sssd/src/main/java/org/freedesktop/dbus/DBusSerializable.java @@ -0,0 +1,39 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; + +import org.freedesktop.dbus.exceptions.DBusException; + +/** + * Custom classes may be sent over DBus if they implement this interface. + *

+ * In addition to the serialize method, classes MUST implement + * a deserialize method which returns null and takes as it's arguments + * all the DBus types the class will be serialied to in order and + * with type parameterisation. They MUST also provide a + * zero-argument constructor. + *

+ *

+ * The serialize method should return the class properties you wish to + * serialize, correctly formatted for the wire + * (DBusConnection.convertParameters() can help with this), in order in an + * Object array. + *

+ *

+ * The deserialize method will be called once after the zero-argument + * constructor. This should contain all the code to initialise the object + * from the types. + *

+ */ +public interface DBusSerializable { + public Object[] serialize() throws DBusException; +} + diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/DBusSigHandler.java b/federation/sssd/src/main/java/org/freedesktop/dbus/DBusSigHandler.java new file mode 100644 index 0000000000..6e40a82059 --- /dev/null +++ b/federation/sssd/src/main/java/org/freedesktop/dbus/DBusSigHandler.java @@ -0,0 +1,27 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; + +/** + * Handle a signal on DBus. + * All Signal handlers are run in their own Thread. + * Application writers are responsible for managing any concurrency issues. + */ +public interface DBusSigHandler { + /** + * Handle a signal. + * + * @param s The signal to handle. If such a class exists, the + * signal will be an instance of the class with the correct type signature. + * Otherwise it will be an instance of DBusSignal + */ + public void handle(T s); +} diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/DBusSignal.java b/federation/sssd/src/main/java/org/freedesktop/dbus/DBusSignal.java new file mode 100644 index 0000000000..96ba6b3e70 --- /dev/null +++ b/federation/sssd/src/main/java/org/freedesktop/dbus/DBusSignal.java @@ -0,0 +1,259 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; + +import cx.ath.matthew.debug.Debug; +import org.freedesktop.dbus.exceptions.DBusException; +import org.freedesktop.dbus.exceptions.MessageFormatException; + +import java.lang.reflect.Constructor; +import java.lang.reflect.GenericDeclaration; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Vector; + +import static org.freedesktop.dbus.Gettext.getString; + +public class DBusSignal extends Message { + DBusSignal() { + } + + public DBusSignal(String source, String path, String iface, String member, String sig, Object... args) throws DBusException { + super(Message.Endian.BIG, Message.MessageType.SIGNAL, (byte) 0); + + if (null == path || null == member || null == iface) + throw new MessageFormatException(getString("missingPathInterfaceSignal")); + headers.put(Message.HeaderField.PATH, path); + headers.put(Message.HeaderField.MEMBER, member); + headers.put(Message.HeaderField.INTERFACE, iface); + + Vector hargs = new Vector(); + hargs.add(new Object[]{Message.HeaderField.PATH, new Object[]{ArgumentType.OBJECT_PATH_STRING, path}}); + hargs.add(new Object[]{Message.HeaderField.INTERFACE, new Object[]{ArgumentType.STRING_STRING, iface}}); + hargs.add(new Object[]{Message.HeaderField.MEMBER, new Object[]{ArgumentType.STRING_STRING, member}}); + + if (null != source) { + headers.put(Message.HeaderField.SENDER, source); + hargs.add(new Object[]{Message.HeaderField.SENDER, new Object[]{ArgumentType.STRING_STRING, source}}); + } + + if (null != sig) { + hargs.add(new Object[]{Message.HeaderField.SIGNATURE, new Object[]{ArgumentType.SIGNATURE_STRING, sig}}); + headers.put(Message.HeaderField.SIGNATURE, sig); + setArgs(args); + } + + blen = new byte[4]; + appendBytes(blen); + append("ua(yv)", ++serial, hargs.toArray()); + pad((byte) 8); + + long c = bytecounter; + if (null != sig) append(sig, args); + marshallint(bytecounter - c, blen, 0, 4); + bodydone = true; + } + + static class internalsig extends DBusSignal { + public internalsig(String source, String objectpath, String type, String name, String sig, Object[] parameters, long serial) throws DBusException { + super(source, objectpath, type, name, sig, parameters, serial); + } + } + + private static Map, Type[]> typeCache = new HashMap, Type[]>(); + private static Map> classCache = new HashMap>(); + private static Map, Constructor> conCache = new HashMap, Constructor>(); + private static Map signames = new HashMap(); + private static Map intnames = new HashMap(); + private Class c; + private boolean bodydone = false; + private byte[] blen; + + static void addInterfaceMap(String java, String dbus) { + intnames.put(dbus, java); + } + + static void addSignalMap(String java, String dbus) { + signames.put(dbus, java); + } + + static DBusSignal createSignal(Class c, String source, String objectpath, String sig, long serial, Object... parameters) throws DBusException { + String type = ""; + if (null != c.getEnclosingClass()) { + if (null != c.getEnclosingClass().getAnnotation(DBusInterfaceName.class)) + type = c.getEnclosingClass().getAnnotation(DBusInterfaceName.class).value(); + else + type = AbstractConnection.dollar_pattern.matcher(c.getEnclosingClass().getName()).replaceAll("."); + + } else + throw new DBusException(getString("signalsMustBeMemberOfClass")); + DBusSignal s = new internalsig(source, objectpath, type, c.getSimpleName(), sig, parameters, serial); + s.c = c; + return s; + } + + @SuppressWarnings("unchecked") + private static Class createSignalClass(String intname, String signame) throws DBusException { + String name = intname + '$' + signame; + Class c = classCache.get(name); + if (null == c) c = DBusMatchRule.getCachedSignalType(name); + if (null != c) return c; + do { + try { + c = (Class) Class.forName(name); + } catch (ClassNotFoundException CNFe) { + } + name = name.replaceAll("\\.([^\\.]*)$", "\\$$1"); + } while (null == c && name.matches(".*\\..*")); + if (null == c) + throw new DBusException(getString("cannotCreateClassFromSignal") + intname + '.' + signame); + classCache.put(name, c); + return c; + } + + @SuppressWarnings("unchecked") + DBusSignal createReal(AbstractConnection conn) throws DBusException { + String intname = intnames.get(getInterface()); + String signame = signames.get(getName()); + if (null == intname) intname = getInterface(); + if (null == signame) signame = getName(); + if (null == c) + c = createSignalClass(intname, signame); + if (Debug.debug) Debug.print(Debug.DEBUG, "Converting signal to type: " + c); + Type[] types = typeCache.get(c); + Constructor con = conCache.get(c); + if (null == types) { + con = (Constructor) c.getDeclaredConstructors()[0]; + conCache.put(c, con); + Type[] ts = con.getGenericParameterTypes(); + types = new Type[ts.length - 1]; + for (int i = 1; i < ts.length; i++) + if (ts[i] instanceof TypeVariable) + for (Type b : ((TypeVariable) ts[i]).getBounds()) + types[i - 1] = b; + else + types[i - 1] = ts[i]; + typeCache.put(c, types); + } + + try { + DBusSignal s; + Object[] args = Marshalling.deSerializeParameters(getParameters(), types, conn); + if (null == args) s = (DBusSignal) con.newInstance(getPath()); + else { + Object[] params = new Object[args.length + 1]; + params[0] = getPath(); + System.arraycopy(args, 0, params, 1, args.length); + + if (Debug.debug) + Debug.print(Debug.DEBUG, "Creating signal of type " + c + " with parameters " + Arrays.deepToString(params)); + s = (DBusSignal) con.newInstance(params); + } + s.headers = headers; + s.wiredata = wiredata; + s.bytecounter = wiredata.length; + return s; + } catch (Exception e) { + if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, e); + throw new DBusException(e.getMessage()); + } + } + + /** + * Create a new signal. + * This contructor MUST be called by all sub classes. + * + * @param objectpath The path to the object this is emitted from. + * @param args The parameters of the signal. + * @throws DBusException This is thrown if the subclass is incorrectly defined. + */ + @SuppressWarnings("unchecked") + protected DBusSignal(String objectpath, Object... args) throws DBusException { + super(Message.Endian.BIG, Message.MessageType.SIGNAL, (byte) 0); + + if (!objectpath.matches(AbstractConnection.OBJECT_REGEX)) + throw new DBusException(getString("invalidObjectPath") + objectpath); + + Class tc = getClass(); + String member; + if (tc.isAnnotationPresent(DBusMemberName.class)) + member = tc.getAnnotation(DBusMemberName.class).value(); + else + member = tc.getSimpleName(); + String iface = null; + Class enc = tc.getEnclosingClass(); + if (null == enc || + !DBusInterface.class.isAssignableFrom(enc) || + enc.getName().equals(enc.getSimpleName())) + throw new DBusException(getString("signalsMustBeMemberOfClass")); + else if (null != enc.getAnnotation(DBusInterfaceName.class)) + iface = enc.getAnnotation(DBusInterfaceName.class).value(); + else + iface = AbstractConnection.dollar_pattern.matcher(enc.getName()).replaceAll("."); + + headers.put(Message.HeaderField.PATH, objectpath); + headers.put(Message.HeaderField.MEMBER, member); + headers.put(Message.HeaderField.INTERFACE, iface); + + Vector hargs = new Vector(); + hargs.add(new Object[]{Message.HeaderField.PATH, new Object[]{ArgumentType.OBJECT_PATH_STRING, objectpath}}); + hargs.add(new Object[]{Message.HeaderField.INTERFACE, new Object[]{ArgumentType.STRING_STRING, iface}}); + hargs.add(new Object[]{Message.HeaderField.MEMBER, new Object[]{ArgumentType.STRING_STRING, member}}); + + String sig = null; + if (0 < args.length) { + try { + Type[] types = typeCache.get(tc); + if (null == types) { + Constructor con = (Constructor) tc.getDeclaredConstructors()[0]; + conCache.put(tc, con); + Type[] ts = con.getGenericParameterTypes(); + types = new Type[ts.length - 1]; + for (int i = 1; i <= types.length; i++) + if (ts[i] instanceof TypeVariable) + types[i - 1] = ((TypeVariable) ts[i]).getBounds()[0]; + else + types[i - 1] = ts[i]; + typeCache.put(tc, types); + } + sig = Marshalling.getDBusType(types); + hargs.add(new Object[]{Message.HeaderField.SIGNATURE, new Object[]{ArgumentType.SIGNATURE_STRING, sig}}); + headers.put(Message.HeaderField.SIGNATURE, sig); + setArgs(args); + } catch (Exception e) { + if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, e); + throw new DBusException(getString("errorAddSignalParameters") + e.getMessage()); + } + } + + blen = new byte[4]; + appendBytes(blen); + append("ua(yv)", ++serial, hargs.toArray()); + pad((byte) 8); + } + + void appendbody(AbstractConnection conn) throws DBusException { + if (bodydone) return; + + Type[] types = typeCache.get(getClass()); + Object[] args = Marshalling.convertParameters(getParameters(), types, conn); + setArgs(args); + String sig = getSig(); + + long c = bytecounter; + if (null != args && 0 < args.length) append(sig, args); + marshallint(bytecounter - c, blen, 0, 4); + bodydone = true; + } +} diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/EfficientMap.java b/federation/sssd/src/main/java/org/freedesktop/dbus/EfficientMap.java new file mode 100644 index 0000000000..32cc7bb5e2 --- /dev/null +++ b/federation/sssd/src/main/java/org/freedesktop/dbus/EfficientMap.java @@ -0,0 +1,122 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; + +/** + * Provides a long => MethodCall map which doesn't allocate objects + * on insertion/removal. Keys must be inserted in ascending order. + */ +class EfficientMap { + private long[] kv; + private MethodCall[] vv; + private int start; + private int end; + private int init_size; + + public EfficientMap(int initial_size) { + init_size = initial_size; + shrink(); + } + + private void grow() { + // create new vectors twice as long + long[] oldkv = kv; + kv = new long[oldkv.length * 2]; + MethodCall[] oldvv = vv; + vv = new MethodCall[oldvv.length * 2]; + + // copy start->length to the start of the new vector + System.arraycopy(oldkv, start, kv, 0, oldkv.length - start); + System.arraycopy(oldvv, start, vv, 0, oldvv.length - start); + // copy 0->end to the next part of the new vector + if (end != (oldkv.length - 1)) { + System.arraycopy(oldkv, 0, kv, oldkv.length - start, end + 1); + System.arraycopy(oldvv, 0, vv, oldvv.length - start, end + 1); + } + // reposition pointers + start = 0; + end = oldkv.length; + } + + // create a new vector with just the valid keys in and return it + public long[] getKeys() { + int size; + if (start < end) size = end - start; + else size = kv.length - (start - end); + long[] lv = new long[size]; + int copya; + if (size > kv.length - start) copya = kv.length - start; + else copya = size; + System.arraycopy(kv, start, lv, 0, copya); + if (copya < size) { + System.arraycopy(kv, 0, lv, copya, size - copya); + } + return lv; + } + + private void shrink() { + if (null != kv && kv.length == init_size) return; + // reset to original size + kv = new long[init_size]; + vv = new MethodCall[init_size]; + start = 0; + end = 0; + } + + public void put(long l, MethodCall m) { + // put this at the end + kv[end] = l; + vv[end] = m; + // move the end + if (end == (kv.length - 1)) end = 0; + else end++; + // if we are out of space, grow. + if (end == start) grow(); + } + + public MethodCall remove(long l) { + // find the item + int pos = find(l); + // if we don't have it return null + if (-1 == pos) return null; + // get the value + MethodCall m = vv[pos]; + // set it as unused + vv[pos] = null; + kv[pos] = -1; + // move the pointer to the first full element + while (-1 == kv[start]) { + if (start == (kv.length - 1)) start = 0; + else start++; + // if we have emptied the list, shrink it + if (start == end) { + shrink(); + break; + } + } + return m; + } + + public boolean contains(long l) { + // check if find succeeds + return -1 != find(l); + } + + /* could binary search, but it's probably the first one */ + private int find(long l) { + int i = start; + while (i != end && kv[i] != l) + if (i == (kv.length - 1)) i = 0; + else i++; + if (i == end) return -1; + return i; + } +} diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/EfficientQueue.java b/federation/sssd/src/main/java/org/freedesktop/dbus/EfficientQueue.java new file mode 100644 index 0000000000..a60c88735c --- /dev/null +++ b/federation/sssd/src/main/java/org/freedesktop/dbus/EfficientQueue.java @@ -0,0 +1,109 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; + +import cx.ath.matthew.debug.Debug; + +/** + * Provides a Message queue which doesn't allocate objects + * on insertion/removal. + */ +class EfficientQueue { + private Message[] mv; + private int start; + private int end; + private int init_size; + + public EfficientQueue(int initial_size) { + init_size = initial_size; + shrink(); + } + + private void grow() { + if (Debug.debug) Debug.print(Debug.DEBUG, "Growing"); + // create new vectors twice as long + Message[] oldmv = mv; + mv = new Message[oldmv.length * 2]; + + // copy start->length to the start of the new vector + System.arraycopy(oldmv, start, mv, 0, oldmv.length - start); + // copy 0->end to the next part of the new vector + if (end != (oldmv.length - 1)) { + System.arraycopy(oldmv, 0, mv, oldmv.length - start, end + 1); + } + // reposition pointers + start = 0; + end = oldmv.length; + } + + // create a new vector with just the valid keys in and return it + public Message[] getKeys() { + if (start == end) return new Message[0]; + Message[] lv; + if (start < end) { + int size = end - start; + lv = new Message[size]; + System.arraycopy(mv, start, lv, 0, size); + } else { + int size = mv.length - start + end; + lv = new Message[size]; + System.arraycopy(mv, start, lv, 0, mv.length - start); + System.arraycopy(mv, 0, lv, mv.length - start, end); + } + return lv; + } + + private void shrink() { + if (Debug.debug) Debug.print(Debug.DEBUG, "Shrinking"); + if (null != mv && mv.length == init_size) return; + // reset to original size + mv = new Message[init_size]; + start = 0; + end = 0; + } + + public void add(Message m) { + if (Debug.debug) Debug.print(Debug.DEBUG, "Enqueueing Message " + m); + // put this at the end + mv[end] = m; + // move the end + if (end == (mv.length - 1)) end = 0; + else end++; + // if we are out of space, grow. + if (end == start) grow(); + } + + public Message remove() { + if (start == end) return null; + // find the item + int pos = start; + // get the value + Message m = mv[pos]; + // set it as unused + mv[pos] = null; + if (start == (mv.length - 1)) start = 0; + else start++; + if (Debug.debug) Debug.print(Debug.DEBUG, "Dequeueing " + m); + return m; + } + + public boolean isEmpty() { + // check if find succeeds + return start == end; + } + + public int size() { + if (end >= start) + return end - start; + else + return mv.length - start + end; + } +} diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/Error.java b/federation/sssd/src/main/java/org/freedesktop/dbus/Error.java new file mode 100644 index 0000000000..3350e9584a --- /dev/null +++ b/federation/sssd/src/main/java/org/freedesktop/dbus/Error.java @@ -0,0 +1,144 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; + +import cx.ath.matthew.debug.Debug; +import org.freedesktop.dbus.exceptions.DBusException; +import org.freedesktop.dbus.exceptions.DBusExecutionException; +import org.freedesktop.dbus.exceptions.MessageFormatException; +import org.freedesktop.dbus.exceptions.NotConnected; + +import java.lang.reflect.Constructor; +import java.util.Vector; + +import static org.freedesktop.dbus.Gettext.getString; + +/** + * Error messages which can be sent over the bus. + */ +public class Error extends Message { + Error() { + } + + public Error(String dest, String errorName, long replyserial, String sig, Object... args) throws DBusException { + this(null, dest, errorName, replyserial, sig, args); + } + + public Error(String source, String dest, String errorName, long replyserial, String sig, Object... args) throws DBusException { + super(Message.Endian.BIG, Message.MessageType.ERROR, (byte) 0); + + if (null == errorName) + throw new MessageFormatException(getString("missingErrorName")); + headers.put(Message.HeaderField.REPLY_SERIAL, replyserial); + headers.put(Message.HeaderField.ERROR_NAME, errorName); + + Vector hargs = new Vector(); + hargs.add(new Object[]{Message.HeaderField.ERROR_NAME, new Object[]{ArgumentType.STRING_STRING, errorName}}); + hargs.add(new Object[]{Message.HeaderField.REPLY_SERIAL, new Object[]{ArgumentType.UINT32_STRING, replyserial}}); + + if (null != source) { + headers.put(Message.HeaderField.SENDER, source); + hargs.add(new Object[]{Message.HeaderField.SENDER, new Object[]{ArgumentType.STRING_STRING, source}}); + } + + if (null != dest) { + headers.put(Message.HeaderField.DESTINATION, dest); + hargs.add(new Object[]{Message.HeaderField.DESTINATION, new Object[]{ArgumentType.STRING_STRING, dest}}); + } + + if (null != sig) { + hargs.add(new Object[]{Message.HeaderField.SIGNATURE, new Object[]{ArgumentType.SIGNATURE_STRING, sig}}); + headers.put(Message.HeaderField.SIGNATURE, sig); + setArgs(args); + } + + byte[] blen = new byte[4]; + appendBytes(blen); + append("ua(yv)", serial, hargs.toArray()); + pad((byte) 8); + + long c = bytecounter; + if (null != sig) append(sig, args); + marshallint(bytecounter - c, blen, 0, 4); + } + + public Error(String source, Message m, Throwable e) throws DBusException { + this(source, m.getSource(), AbstractConnection.dollar_pattern.matcher(e.getClass().getName()).replaceAll("."), m.getSerial(), "s", e.getMessage()); + } + + public Error(Message m, Throwable e) throws DBusException { + this(m.getSource(), AbstractConnection.dollar_pattern.matcher(e.getClass().getName()).replaceAll("."), m.getSerial(), "s", e.getMessage()); + } + + @SuppressWarnings("unchecked") + private static Class createExceptionClass(String name) { + if (name == "org.freedesktop.DBus.Local.disconnected") return NotConnected.class; + Class c = null; + do { + try { + c = (Class) Class.forName(name); + } catch (ClassNotFoundException CNFe) { + } + name = name.replaceAll("\\.([^\\.]*)$", "\\$$1"); + } while (null == c && name.matches(".*\\..*")); + return c; + } + + /** + * Turns this into an exception of the correct type + */ + public DBusExecutionException getException() { + try { + Class c = createExceptionClass(getName()); + if (null == c || !DBusExecutionException.class.isAssignableFrom(c)) c = DBusExecutionException.class; + Constructor con = c.getConstructor(String.class); + DBusExecutionException ex; + Object[] args = getParameters(); + if (null == args || 0 == args.length) + ex = con.newInstance(""); + else { + String s = ""; + for (Object o : args) + s += o + " "; + ex = con.newInstance(s.trim()); + } + ex.setType(getName()); + return ex; + } catch (Exception e) { + if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, e); + if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug && null != e.getCause()) + Debug.print(Debug.ERR, e.getCause()); + DBusExecutionException ex; + Object[] args = null; + try { + args = getParameters(); + } catch (Exception ee) { + } + if (null == args || 0 == args.length) + ex = new DBusExecutionException(""); + else { + String s = ""; + for (Object o : args) + s += o + " "; + ex = new DBusExecutionException(s.trim()); + } + ex.setType(getName()); + return ex; + } + } + + /** + * Throw this as an exception of the correct type + */ + public void throwException() throws DBusExecutionException { + throw getException(); + } +} diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/ExportedObject.java b/federation/sssd/src/main/java/org/freedesktop/dbus/ExportedObject.java new file mode 100644 index 0000000000..b9719a1515 --- /dev/null +++ b/federation/sssd/src/main/java/org/freedesktop/dbus/ExportedObject.java @@ -0,0 +1,165 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; + +import org.freedesktop.dbus.exceptions.DBusException; +import org.freedesktop.dbus.exceptions.DBusExecutionException; + +import java.lang.annotation.Annotation; +import java.lang.ref.Reference; +import java.lang.ref.WeakReference; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.HashMap; +import java.util.Map; + +import static org.freedesktop.dbus.Gettext.getString; + +class ExportedObject { + @SuppressWarnings("unchecked") + private String getAnnotations(AnnotatedElement c) { + String ans = ""; + for (Annotation a : c.getDeclaredAnnotations()) { + Class t = a.annotationType(); + String value = ""; + try { + Method m = t.getMethod("value"); + value = m.invoke(a).toString(); + } catch (NoSuchMethodException NSMe) { + } catch (InvocationTargetException ITe) { + } catch (IllegalAccessException IAe) { + } + + ans += " \n"; + } + return ans; + } + + @SuppressWarnings("unchecked") + private Map getExportedMethods(Class c) throws DBusException { + if (DBusInterface.class.equals(c)) return new HashMap(); + Map m = new HashMap(); + for (Class i : c.getInterfaces()) + if (DBusInterface.class.equals(i)) { + // add this class's public methods + if (null != c.getAnnotation(DBusInterfaceName.class)) { + String name = ((DBusInterfaceName) c.getAnnotation(DBusInterfaceName.class)).value(); + introspectiondata += " \n"; + DBusSignal.addInterfaceMap(c.getName(), name); + } else { + // don't let people export things which don't have a + // valid D-Bus interface name + if (c.getName().equals(c.getSimpleName())) + throw new DBusException(getString("interfaceNotAllowedOutsidePackage")); + if (c.getName().length() > DBusConnection.MAX_NAME_LENGTH) + throw new DBusException(getString("introspectInterfaceExceedCharacters") + c.getName()); + else + introspectiondata += " \n"; + } + introspectiondata += getAnnotations(c); + for (Method meth : c.getDeclaredMethods()) + if (Modifier.isPublic(meth.getModifiers())) { + String ms = ""; + String name; + if (meth.isAnnotationPresent(DBusMemberName.class)) + name = meth.getAnnotation(DBusMemberName.class).value(); + else + name = meth.getName(); + if (name.length() > DBusConnection.MAX_NAME_LENGTH) + throw new DBusException(getString("introspectMethodExceedCharacters") + name); + introspectiondata += " \n"; + introspectiondata += getAnnotations(meth); + for (Class ex : meth.getExceptionTypes()) + if (DBusExecutionException.class.isAssignableFrom(ex)) + introspectiondata += + " \n"; + for (Type pt : meth.getGenericParameterTypes()) + for (String s : Marshalling.getDBusType(pt)) { + introspectiondata += " \n"; + ms += s; + } + if (!Void.TYPE.equals(meth.getGenericReturnType())) { + if (Tuple.class.isAssignableFrom((Class) meth.getReturnType())) { + ParameterizedType tc = (ParameterizedType) meth.getGenericReturnType(); + Type[] ts = tc.getActualTypeArguments(); + + for (Type t : ts) + if (t != null) + for (String s : Marshalling.getDBusType(t)) + introspectiondata += " \n"; + } else if (Object[].class.equals(meth.getGenericReturnType())) { + throw new DBusException(getString("cannotIntrospectReturnType")); + } else + for (String s : Marshalling.getDBusType(meth.getGenericReturnType())) + introspectiondata += " \n"; + } + introspectiondata += " \n"; + m.put(new MethodTuple(name, ms), meth); + } + for (Class sig : c.getDeclaredClasses()) + if (DBusSignal.class.isAssignableFrom(sig)) { + String name; + if (sig.isAnnotationPresent(DBusMemberName.class)) { + name = ((DBusMemberName) sig.getAnnotation(DBusMemberName.class)).value(); + DBusSignal.addSignalMap(sig.getSimpleName(), name); + } else + name = sig.getSimpleName(); + if (name.length() > DBusConnection.MAX_NAME_LENGTH) + throw new DBusException(getString("introspectSignalExceedCharacters") + name); + introspectiondata += " \n"; + Constructor con = sig.getConstructors()[0]; + Type[] ts = con.getGenericParameterTypes(); + for (int j = 1; j < ts.length; j++) + for (String s : Marshalling.getDBusType(ts[j])) + introspectiondata += " \n"; + introspectiondata += getAnnotations(sig); + introspectiondata += " \n"; + + } + introspectiondata += " \n"; + } else { + // recurse + m.putAll(getExportedMethods(i)); + } + return m; + } + + Map methods; + Reference object; + String introspectiondata; + + public ExportedObject(DBusInterface object, boolean weakreferences) throws DBusException { + if (weakreferences) + this.object = new WeakReference(object); + else + this.object = new StrongReference(object); + introspectiondata = ""; + methods = getExportedMethods(object.getClass()); + introspectiondata += + " \n" + + " \n" + + " \n" + + " \n" + + " \n"; + introspectiondata += + " \n" + + " \n" + + " \n" + + " \n"; + } +} + + diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/Gettext.java b/federation/sssd/src/main/java/org/freedesktop/dbus/Gettext.java new file mode 100644 index 0000000000..a519a6f9a3 --- /dev/null +++ b/federation/sssd/src/main/java/org/freedesktop/dbus/Gettext.java @@ -0,0 +1,30 @@ +/* + * Pescetti Pseudo-Duplimate Generator + * + * Copyright (C) 2007 Matthew Johnson + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License Version 2 as published by + * the Free Software Foundation. This program is distributed in the hope that + * it will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. You should have received a + * copy of the GNU Lesser General Public License along with this program; if not, + * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * To Contact me, please email src@matthew.ath.cx + * + */ +package org.freedesktop.dbus; + +import java.util.ResourceBundle; + +public class Gettext { + private static ResourceBundle myResources = + ResourceBundle.getBundle("en_US"); + + public static String getString(String s) { + return myResources.getString(s); + } +} diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/InternalSignal.java b/federation/sssd/src/main/java/org/freedesktop/dbus/InternalSignal.java new file mode 100644 index 0000000000..0a3072d477 --- /dev/null +++ b/federation/sssd/src/main/java/org/freedesktop/dbus/InternalSignal.java @@ -0,0 +1,20 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; + +import org.freedesktop.dbus.exceptions.DBusException; + +class InternalSignal extends DBusSignal { + public InternalSignal(String source, String objectpath, String name, String iface, String sig, long serial, Object... parameters) throws DBusException { + super(objectpath, iface, name, sig, parameters); + this.serial = serial; + } +} diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/Marshalling.java b/federation/sssd/src/main/java/org/freedesktop/dbus/Marshalling.java new file mode 100644 index 0000000000..9e88309b10 --- /dev/null +++ b/federation/sssd/src/main/java/org/freedesktop/dbus/Marshalling.java @@ -0,0 +1,629 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; + +import cx.ath.matthew.debug.Debug; +import org.freedesktop.dbus.exceptions.DBusException; +import org.freedesktop.dbus.types.DBusListType; +import org.freedesktop.dbus.types.DBusMapType; +import org.freedesktop.dbus.types.DBusStructType; + +import java.lang.reflect.Array; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.GenericArrayType; +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.text.MessageFormat; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Vector; + +import static org.freedesktop.dbus.Gettext.getString; + +/** + * Contains static methods for marshalling values. + */ +public class Marshalling { + private static Map typeCache = new HashMap(); + + /** + * Will return the DBus type corresponding to the given Java type. + * Note, container type should have their ParameterizedType not their + * Class passed in here. + * + * @param c The Java types. + * @return The DBus types. + * @throws DBusException If the given type cannot be converted to a DBus type. + */ + public static String getDBusType(Type[] c) throws DBusException { + StringBuffer sb = new StringBuffer(); + for (Type t : c) + for (String s : getDBusType(t)) + sb.append(s); + return sb.toString(); + } + + /** + * Will return the DBus type corresponding to the given Java type. + * Note, container type should have their ParameterizedType not their + * Class passed in here. + * + * @param c The Java type. + * @return The DBus type. + * @throws DBusException If the given type cannot be converted to a DBus type. + */ + public static String[] getDBusType(Type c) throws DBusException { + String[] cached = typeCache.get(c); + if (null != cached) return cached; + cached = getDBusType(c, false); + typeCache.put(c, cached); + return cached; + } + + /** + * Will return the DBus type corresponding to the given Java type. + * Note, container type should have their ParameterizedType not their + * Class passed in here. + * + * @param c The Java type. + * @param basic If true enforces this to be a non-compound type. (compound types are Maps, Structs and Lists/arrays). + * @return The DBus type. + * @throws DBusException If the given type cannot be converted to a DBus type. + */ + public static String[] getDBusType(Type c, boolean basic) throws DBusException { + return recursiveGetDBusType(c, basic, 0); + } + + private static StringBuffer[] out = new StringBuffer[10]; + + @SuppressWarnings("unchecked") + public static String[] recursiveGetDBusType(Type c, boolean basic, int level) throws DBusException { + if (out.length <= level) { + StringBuffer[] newout = new StringBuffer[out.length]; + System.arraycopy(out, 0, newout, 0, out.length); + out = newout; + } + if (null == out[level]) out[level] = new StringBuffer(); + else out[level].delete(0, out[level].length()); + + if (basic && !(c instanceof Class)) + throw new DBusException(c + getString("notBasicType")); + + if (c instanceof TypeVariable) out[level].append((char) Message.ArgumentType.VARIANT); + else if (c instanceof GenericArrayType) { + out[level].append((char) Message.ArgumentType.ARRAY); + String[] s = recursiveGetDBusType(((GenericArrayType) c).getGenericComponentType(), false, level + 1); + if (s.length != 1) throw new DBusException(getString("multiValuedArrayNotPermitted")); + out[level].append(s[0]); + } else if ((c instanceof Class && + DBusSerializable.class.isAssignableFrom((Class) c)) || + (c instanceof ParameterizedType && + DBusSerializable.class.isAssignableFrom((Class) ((ParameterizedType) c).getRawType()))) { + // it's a custom serializable type + Type[] newtypes = null; + if (c instanceof Class) { + for (Method m : ((Class) c).getDeclaredMethods()) + if (m.getName().equals("deserialize")) + newtypes = m.getGenericParameterTypes(); + } else + for (Method m : ((Class) ((ParameterizedType) c).getRawType()).getDeclaredMethods()) + if (m.getName().equals("deserialize")) + newtypes = m.getGenericParameterTypes(); + + if (null == newtypes) throw new DBusException(getString("mustImplementDeserializeMethod")); + + String[] sigs = new String[newtypes.length]; + for (int j = 0; j < sigs.length; j++) { + String[] ss = recursiveGetDBusType(newtypes[j], false, level + 1); + if (1 != ss.length) throw new DBusException(getString("mustSerializeNativeDBusTypes")); + sigs[j] = ss[0]; + } + return sigs; + } else if (c instanceof ParameterizedType) { + ParameterizedType p = (ParameterizedType) c; + if (p.getRawType().equals(Map.class)) { + out[level].append("a{"); + Type[] t = p.getActualTypeArguments(); + try { + String[] s = recursiveGetDBusType(t[0], true, level + 1); + if (s.length != 1) throw new DBusException(getString("multiValuedArrayNotPermitted")); + out[level].append(s[0]); + s = recursiveGetDBusType(t[1], false, level + 1); + if (s.length != 1) throw new DBusException(getString("multiValuedArrayNotPermitted")); + out[level].append(s[0]); + } catch (ArrayIndexOutOfBoundsException AIOOBe) { + if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, AIOOBe); + throw new DBusException(getString("mapParameters")); + } + out[level].append('}'); + } else if (List.class.isAssignableFrom((Class) p.getRawType())) { + for (Type t : p.getActualTypeArguments()) { + if (Type.class.equals(t)) + out[level].append((char) Message.ArgumentType.SIGNATURE); + else { + String[] s = recursiveGetDBusType(t, false, level + 1); + if (s.length != 1) throw new DBusException(getString("multiValuedArrayNotPermitted")); + out[level].append((char) Message.ArgumentType.ARRAY); + out[level].append(s[0]); + } + } + } else if (p.getRawType().equals(Variant.class)) { + out[level].append((char) Message.ArgumentType.VARIANT); + } else if (DBusInterface.class.isAssignableFrom((Class) p.getRawType())) { + out[level].append((char) Message.ArgumentType.OBJECT_PATH); + } else if (Tuple.class.isAssignableFrom((Class) p.getRawType())) { + Type[] ts = p.getActualTypeArguments(); + Vector vs = new Vector(); + for (Type t : ts) + for (String s : recursiveGetDBusType(t, false, level + 1)) + vs.add(s); + return vs.toArray(new String[0]); + } else + throw new DBusException(getString("nonExportableParameterizedType") + c); + } else if (c.equals(Byte.class)) out[level].append((char) Message.ArgumentType.BYTE); + else if (c.equals(Byte.TYPE)) out[level].append((char) Message.ArgumentType.BYTE); + else if (c.equals(Boolean.class)) out[level].append((char) Message.ArgumentType.BOOLEAN); + else if (c.equals(Boolean.TYPE)) out[level].append((char) Message.ArgumentType.BOOLEAN); + else if (c.equals(Short.class)) out[level].append((char) Message.ArgumentType.INT16); + else if (c.equals(Short.TYPE)) out[level].append((char) Message.ArgumentType.INT16); + else if (c.equals(UInt16.class)) out[level].append((char) Message.ArgumentType.UINT16); + else if (c.equals(Integer.class)) out[level].append((char) Message.ArgumentType.INT32); + else if (c.equals(Integer.TYPE)) out[level].append((char) Message.ArgumentType.INT32); + else if (c.equals(UInt32.class)) out[level].append((char) Message.ArgumentType.UINT32); + else if (c.equals(Long.class)) out[level].append((char) Message.ArgumentType.INT64); + else if (c.equals(Long.TYPE)) out[level].append((char) Message.ArgumentType.INT64); + else if (c.equals(UInt64.class)) out[level].append((char) Message.ArgumentType.UINT64); + else if (c.equals(Double.class)) out[level].append((char) Message.ArgumentType.DOUBLE); + else if (c.equals(Double.TYPE)) out[level].append((char) Message.ArgumentType.DOUBLE); + else if (c.equals(Float.class) && AbstractConnection.FLOAT_SUPPORT) + out[level].append((char) Message.ArgumentType.FLOAT); + else if (c.equals(Float.class)) out[level].append((char) Message.ArgumentType.DOUBLE); + else if (c.equals(Float.TYPE) && AbstractConnection.FLOAT_SUPPORT) + out[level].append((char) Message.ArgumentType.FLOAT); + else if (c.equals(Float.TYPE)) out[level].append((char) Message.ArgumentType.DOUBLE); + else if (c.equals(String.class)) out[level].append((char) Message.ArgumentType.STRING); + else if (c.equals(Variant.class)) out[level].append((char) Message.ArgumentType.VARIANT); + else if (c instanceof Class && + DBusInterface.class.isAssignableFrom((Class) c)) + out[level].append((char) Message.ArgumentType.OBJECT_PATH); + else if (c instanceof Class && + Path.class.equals((Class) c)) + out[level].append((char) Message.ArgumentType.OBJECT_PATH); + else if (c instanceof Class && + ObjectPath.class.equals((Class) c)) + out[level].append((char) Message.ArgumentType.OBJECT_PATH); + else if (c instanceof Class && + ((Class) c).isArray()) { + if (Type.class.equals(((Class) c).getComponentType())) + out[level].append((char) Message.ArgumentType.SIGNATURE); + else { + out[level].append((char) Message.ArgumentType.ARRAY); + String[] s = recursiveGetDBusType(((Class) c).getComponentType(), false, level + 1); + if (s.length != 1) throw new DBusException(getString("multiValuedArrayNotPermitted")); + out[level].append(s[0]); + } + } else if (c instanceof Class && + Struct.class.isAssignableFrom((Class) c)) { + out[level].append((char) Message.ArgumentType.STRUCT1); + Type[] ts = Container.getTypeCache(c); + if (null == ts) { + Field[] fs = ((Class) c).getDeclaredFields(); + ts = new Type[fs.length]; + for (Field f : fs) { + Position p = f.getAnnotation(Position.class); + if (null == p) continue; + ts[p.value()] = f.getGenericType(); + } + Container.putTypeCache(c, ts); + } + + for (Type t : ts) + if (t != null) + for (String s : recursiveGetDBusType(t, false, level + 1)) + out[level].append(s); + out[level].append(')'); + } else { + throw new DBusException(getString("nonExportableType") + c); + } + + if (Debug.debug) Debug.print(Debug.VERBOSE, "Converted Java type: " + c + " to D-Bus Type: " + out[level]); + + return new String[]{out[level].toString()}; + } + + /** + * Converts a dbus type string into Java Type objects, + * + * @param dbus The DBus type or types. + * @param rv Vector to return the types in. + * @param limit Maximum number of types to parse (-1 == nolimit). + * @return number of characters parsed from the type string. + */ + public static int getJavaType(String dbus, List rv, int limit) throws DBusException { + if (null == dbus || "".equals(dbus) || 0 == limit) return 0; + + try { + int i = 0; + for (; i < dbus.length() && (-1 == limit || limit > rv.size()); i++) + switch (dbus.charAt(i)) { + case Message.ArgumentType.STRUCT1: + int j = i + 1; + for (int c = 1; c > 0; j++) { + if (')' == dbus.charAt(j)) c--; + else if (Message.ArgumentType.STRUCT1 == dbus.charAt(j)) c++; + } + + Vector contained = new Vector(); + int c = getJavaType(dbus.substring(i + 1, j - 1), contained, -1); + rv.add(new DBusStructType(contained.toArray(new Type[0]))); + i = j; + break; + case Message.ArgumentType.ARRAY: + if (Message.ArgumentType.DICT_ENTRY1 == dbus.charAt(i + 1)) { + contained = new Vector(); + c = getJavaType(dbus.substring(i + 2), contained, 2); + rv.add(new DBusMapType(contained.get(0), contained.get(1))); + i += (c + 2); + } else { + contained = new Vector(); + c = getJavaType(dbus.substring(i + 1), contained, 1); + rv.add(new DBusListType(contained.get(0))); + i += c; + } + break; + case Message.ArgumentType.VARIANT: + rv.add(Variant.class); + break; + case Message.ArgumentType.BOOLEAN: + rv.add(Boolean.class); + break; + case Message.ArgumentType.INT16: + rv.add(Short.class); + break; + case Message.ArgumentType.BYTE: + rv.add(Byte.class); + break; + case Message.ArgumentType.OBJECT_PATH: + rv.add(DBusInterface.class); + break; + case Message.ArgumentType.UINT16: + rv.add(UInt16.class); + break; + case Message.ArgumentType.INT32: + rv.add(Integer.class); + break; + case Message.ArgumentType.UINT32: + rv.add(UInt32.class); + break; + case Message.ArgumentType.INT64: + rv.add(Long.class); + break; + case Message.ArgumentType.UINT64: + rv.add(UInt64.class); + break; + case Message.ArgumentType.DOUBLE: + rv.add(Double.class); + break; + case Message.ArgumentType.FLOAT: + rv.add(Float.class); + break; + case Message.ArgumentType.STRING: + rv.add(String.class); + break; + case Message.ArgumentType.SIGNATURE: + rv.add(Type[].class); + break; + case Message.ArgumentType.DICT_ENTRY1: + rv.add(Map.Entry.class); + contained = new Vector(); + c = getJavaType(dbus.substring(i + 1), contained, 2); + i += c + 1; + break; + default: + throw new DBusException(MessageFormat.format(getString("parseDBusSignatureFailure"), new Object[]{dbus, dbus.charAt(i)})); + } + return i; + } catch (IndexOutOfBoundsException IOOBe) { + if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, IOOBe); + throw new DBusException(getString("parseDBusTypeSignatureFailure") + dbus); + } + } + + /** + * Recursively converts types for serialization onto DBus. + * + * @param parameters The parameters to convert. + * @param types The (possibly generic) types of the parameters. + * @return The converted parameters. + * @throws DBusException Thrown if there is an error in converting the objects. + */ + @SuppressWarnings("unchecked") + public static Object[] convertParameters(Object[] parameters, Type[] types, AbstractConnection conn) throws DBusException { + if (null == parameters) return null; + for (int i = 0; i < parameters.length; i++) { + if (Debug.debug) + Debug.print(Debug.VERBOSE, "Converting " + i + " from " + parameters[i] + " to " + types[i]); + if (null == parameters[i]) continue; + + if (parameters[i] instanceof DBusSerializable) { + for (Method m : parameters[i].getClass().getDeclaredMethods()) + if (m.getName().equals("deserialize")) { + Type[] newtypes = m.getParameterTypes(); + Type[] expand = new Type[types.length + newtypes.length - 1]; + System.arraycopy(types, 0, expand, 0, i); + System.arraycopy(newtypes, 0, expand, i, newtypes.length); + System.arraycopy(types, i + 1, expand, i + newtypes.length, types.length - i - 1); + types = expand; + Object[] newparams = ((DBusSerializable) parameters[i]).serialize(); + Object[] exparams = new Object[parameters.length + newparams.length - 1]; + System.arraycopy(parameters, 0, exparams, 0, i); + System.arraycopy(newparams, 0, exparams, i, newparams.length); + System.arraycopy(parameters, i + 1, exparams, i + newparams.length, parameters.length - i - 1); + parameters = exparams; + } + i--; + } else if (parameters[i] instanceof Tuple) { + Type[] newtypes = ((ParameterizedType) types[i]).getActualTypeArguments(); + Type[] expand = new Type[types.length + newtypes.length - 1]; + System.arraycopy(types, 0, expand, 0, i); + System.arraycopy(newtypes, 0, expand, i, newtypes.length); + System.arraycopy(types, i + 1, expand, i + newtypes.length, types.length - i - 1); + types = expand; + Object[] newparams = ((Tuple) parameters[i]).getParameters(); + Object[] exparams = new Object[parameters.length + newparams.length - 1]; + System.arraycopy(parameters, 0, exparams, 0, i); + System.arraycopy(newparams, 0, exparams, i, newparams.length); + System.arraycopy(parameters, i + 1, exparams, i + newparams.length, parameters.length - i - 1); + parameters = exparams; + if (Debug.debug) + Debug.print(Debug.VERBOSE, "New params: " + Arrays.deepToString(parameters) + " new types: " + Arrays.deepToString(types)); + i--; + } else if (types[i] instanceof TypeVariable && + !(parameters[i] instanceof Variant)) + // its an unwrapped variant, wrap it + parameters[i] = new Variant(parameters[i]); + else if (parameters[i] instanceof DBusInterface) + parameters[i] = conn.getExportedObject((DBusInterface) parameters[i]); + } + return parameters; + } + + @SuppressWarnings("unchecked") + static Object deSerializeParameter(Object parameter, Type type, AbstractConnection conn) throws Exception { + if (Debug.debug) + Debug.print(Debug.VERBOSE, "Deserializing from " + parameter.getClass() + " to " + type.getClass()); + if (null == parameter) + return null; + + // its a wrapped variant, unwrap it + if (type instanceof TypeVariable + && parameter instanceof Variant) { + parameter = ((Variant) parameter).getValue(); + } + + // Turn a signature into a Type[] + if (type instanceof Class + && ((Class) type).isArray() + && ((Class) type).getComponentType().equals(Type.class) + && parameter instanceof String) { + Vector rv = new Vector(); + getJavaType((String) parameter, rv, -1); + parameter = rv.toArray(new Type[0]); + } + + // its an object path, get/create the proxy + if (parameter instanceof ObjectPath) { + if (type instanceof Class && DBusInterface.class.isAssignableFrom((Class) type)) + parameter = conn.getExportedObject( + ((ObjectPath) parameter).source, + ((ObjectPath) parameter).path); + else + parameter = new Path(((ObjectPath) parameter).path); + } + + // it should be a struct. create it + if (parameter instanceof Object[] && + type instanceof Class && + Struct.class.isAssignableFrom((Class) type)) { + if (Debug.debug) Debug.print(Debug.VERBOSE, "Creating Struct " + type + " from " + parameter); + Type[] ts = Container.getTypeCache(type); + if (null == ts) { + Field[] fs = ((Class) type).getDeclaredFields(); + ts = new Type[fs.length]; + for (Field f : fs) { + Position p = f.getAnnotation(Position.class); + if (null == p) continue; + ts[p.value()] = f.getGenericType(); + } + Container.putTypeCache(type, ts); + } + + // recurse over struct contents + parameter = deSerializeParameters((Object[]) parameter, ts, conn); + for (Constructor con : ((Class) type).getDeclaredConstructors()) { + try { + parameter = con.newInstance((Object[]) parameter); + break; + } catch (IllegalArgumentException IAe) { + } + } + } + + // recurse over arrays + if (parameter instanceof Object[]) { + Type[] ts = new Type[((Object[]) parameter).length]; + Arrays.fill(ts, parameter.getClass().getComponentType()); + parameter = deSerializeParameters((Object[]) parameter, + ts, conn); + } + if (parameter instanceof List) { + Type type2; + if (type instanceof ParameterizedType) + type2 = ((ParameterizedType) type).getActualTypeArguments()[0]; + else if (type instanceof GenericArrayType) + type2 = ((GenericArrayType) type).getGenericComponentType(); + else if (type instanceof Class && ((Class) type).isArray()) + type2 = ((Class) type).getComponentType(); + else + type2 = null; + if (null != type2) + parameter = deSerializeParameters((List) parameter, type2, conn); + } + + // correct floats if appropriate + if (type.equals(Float.class) || type.equals(Float.TYPE)) + if (!(parameter instanceof Float)) + parameter = ((Number) parameter).floatValue(); + + // make sure arrays are in the correct format + if (parameter instanceof Object[] || + parameter instanceof List || + parameter.getClass().isArray()) { + if (type instanceof ParameterizedType) + parameter = ArrayFrob.convert(parameter, + (Class) ((ParameterizedType) type).getRawType()); + else if (type instanceof GenericArrayType) { + Type ct = ((GenericArrayType) type).getGenericComponentType(); + Class cc = null; + if (ct instanceof Class) + cc = (Class) ct; + if (ct instanceof ParameterizedType) + cc = (Class) ((ParameterizedType) ct).getRawType(); + Object o = Array.newInstance(cc, 0); + parameter = ArrayFrob.convert(parameter, + o.getClass()); + } else if (type instanceof Class && + ((Class) type).isArray()) { + Class cc = ((Class) type).getComponentType(); + if ((cc.equals(Float.class) || cc.equals(Float.TYPE)) + && (parameter instanceof double[])) { + double[] tmp1 = (double[]) parameter; + float[] tmp2 = new float[tmp1.length]; + for (int i = 0; i < tmp1.length; i++) + tmp2[i] = (float) tmp1[i]; + parameter = tmp2; + } + Object o = Array.newInstance(cc, 0); + parameter = ArrayFrob.convert(parameter, + o.getClass()); + } + } + if (parameter instanceof DBusMap) { + if (Debug.debug) Debug.print(Debug.VERBOSE, "Deserializing a Map"); + DBusMap dmap = (DBusMap) parameter; + Type[] maptypes = ((ParameterizedType) type).getActualTypeArguments(); + for (int i = 0; i < dmap.entries.length; i++) { + dmap.entries[i][0] = deSerializeParameter(dmap.entries[i][0], maptypes[0], conn); + dmap.entries[i][1] = deSerializeParameter(dmap.entries[i][1], maptypes[1], conn); + } + } + return parameter; + } + + static List deSerializeParameters(List parameters, Type type, AbstractConnection conn) throws Exception { + if (Debug.debug) Debug.print(Debug.VERBOSE, "Deserializing from " + parameters + " to " + type); + if (null == parameters) return null; + for (int i = 0; i < parameters.size(); i++) { + if (null == parameters.get(i)) continue; + + /* DO NOT DO THIS! IT'S REALLY NOT SUPPORTED! + * if (type instanceof Class && + DBusSerializable.class.isAssignableFrom((Class) types[i])) { + for (Method m: ((Class) types[i]).getDeclaredMethods()) + if (m.getName().equals("deserialize")) { + Type[] newtypes = m.getGenericParameterTypes(); + try { + Object[] sub = new Object[newtypes.length]; + System.arraycopy(parameters, i, sub, 0, newtypes.length); + sub = deSerializeParameters(sub, newtypes, conn); + DBusSerializable sz = (DBusSerializable) ((Class) types[i]).newInstance(); + m.invoke(sz, sub); + Object[] compress = new Object[parameters.length - newtypes.length + 1]; + System.arraycopy(parameters, 0, compress, 0, i); + compress[i] = sz; + System.arraycopy(parameters, i + newtypes.length, compress, i+1, parameters.length - i - newtypes.length); + parameters = compress; + } catch (ArrayIndexOutOfBoundsException AIOOBe) { + if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, AIOOBe); + throw new DBusException("Not enough elements to create custom object from serialized data ("+(parameters.size()-i)+" < "+(newtypes.length)+")"); + } + } + } else*/ + parameters.set(i, deSerializeParameter(parameters.get(i), type, conn)); + } + return parameters; + } + + @SuppressWarnings("unchecked") + static Object[] deSerializeParameters(Object[] parameters, Type[] types, AbstractConnection conn) throws Exception { + if (Debug.debug) + Debug.print(Debug.VERBOSE, "Deserializing from " + Arrays.deepToString(parameters) + " to " + Arrays.deepToString(types)); + if (null == parameters) return null; + + if (types.length == 1 && types[0] instanceof ParameterizedType + && Tuple.class.isAssignableFrom((Class) ((ParameterizedType) types[0]).getRawType())) { + types = ((ParameterizedType) types[0]).getActualTypeArguments(); + } + + for (int i = 0; i < parameters.length; i++) { + // CHECK IF ARRAYS HAVE THE SAME LENGTH <-- has to happen after expanding parameters + if (i >= types.length) { + if (Debug.debug) { + for (int j = 0; j < parameters.length; j++) { + Debug.print(Debug.ERR, String.format("Error, Parameters difference (%1d, '%2s')", j, parameters[j].toString())); + } + } + throw new DBusException(getString("errorDeserializingMessage")); + } + if (null == parameters[i]) continue; + + if ((types[i] instanceof Class && + DBusSerializable.class.isAssignableFrom((Class) types[i])) || + (types[i] instanceof ParameterizedType && + DBusSerializable.class.isAssignableFrom((Class) ((ParameterizedType) types[i]).getRawType()))) { + Class dsc; + if (types[i] instanceof Class) + dsc = (Class) types[i]; + else + dsc = (Class) ((ParameterizedType) types[i]).getRawType(); + for (Method m : dsc.getDeclaredMethods()) + if (m.getName().equals("deserialize")) { + Type[] newtypes = m.getGenericParameterTypes(); + try { + Object[] sub = new Object[newtypes.length]; + System.arraycopy(parameters, i, sub, 0, newtypes.length); + sub = deSerializeParameters(sub, newtypes, conn); + DBusSerializable sz = dsc.newInstance(); + m.invoke(sz, sub); + Object[] compress = new Object[parameters.length - newtypes.length + 1]; + System.arraycopy(parameters, 0, compress, 0, i); + compress[i] = sz; + System.arraycopy(parameters, i + newtypes.length, compress, i + 1, parameters.length - i - newtypes.length); + parameters = compress; + } catch (ArrayIndexOutOfBoundsException AIOOBe) { + if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, AIOOBe); + throw new DBusException(MessageFormat.format(getString("notEnoughElementsToCreateCustomObject"), + new Object[]{parameters.length - i, newtypes.length})); + } + } + } else + parameters[i] = deSerializeParameter(parameters[i], types[i], conn); + } + return parameters; + } +} + + diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/Message.java b/federation/sssd/src/main/java/org/freedesktop/dbus/Message.java new file mode 100644 index 0000000000..01f00e8823 --- /dev/null +++ b/federation/sssd/src/main/java/org/freedesktop/dbus/Message.java @@ -0,0 +1,1216 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; + +import cx.ath.matthew.debug.Debug; +import cx.ath.matthew.utils.Hexdump; +import org.freedesktop.dbus.exceptions.DBusException; +import org.freedesktop.dbus.exceptions.MarshallingException; +import org.freedesktop.dbus.exceptions.UnknownTypeCodeException; + +import java.io.UnsupportedEncodingException; +import java.lang.reflect.Array; +import java.lang.reflect.Type; +import java.text.MessageFormat; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Vector; + +import static org.freedesktop.dbus.Gettext.getString; + +/** + * Superclass of all messages which are sent over the Bus. + * This class deals with all the marshalling to/from the wire format. + */ +public class Message { + /** + * Defines constants representing the endianness of the message. + */ + public static interface Endian { + public static final byte BIG = 'B'; + public static final byte LITTLE = 'l'; + } + + /** + * Defines constants representing the flags which can be set on a message. + */ + public static interface Flags { + public static final byte NO_REPLY_EXPECTED = 0x01; + public static final byte NO_AUTO_START = 0x02; + public static final byte ASYNC = 0x40; + } + + /** + * Defines constants for each message type. + */ + public static interface MessageType { + public static final byte METHOD_CALL = 1; + public static final byte METHOD_RETURN = 2; + public static final byte ERROR = 3; + public static final byte SIGNAL = 4; + } + + /** + * The current protocol major version. + */ + public static final byte PROTOCOL = 1; + + /** + * Defines constants for each valid header field type. + */ + public static interface HeaderField { + public static final byte PATH = 1; + public static final byte INTERFACE = 2; + public static final byte MEMBER = 3; + public static final byte ERROR_NAME = 4; + public static final byte REPLY_SERIAL = 5; + public static final byte DESTINATION = 6; + public static final byte SENDER = 7; + public static final byte SIGNATURE = 8; + } + + /** + * Defines constants for each argument type. + * There are two constants for each argument type, + * as a byte or as a String (the _STRING version) + */ + public static interface ArgumentType { + public static final String BYTE_STRING = "y"; + public static final String BOOLEAN_STRING = "b"; + public static final String INT16_STRING = "n"; + public static final String UINT16_STRING = "q"; + public static final String INT32_STRING = "i"; + public static final String UINT32_STRING = "u"; + public static final String INT64_STRING = "x"; + public static final String UINT64_STRING = "t"; + public static final String DOUBLE_STRING = "d"; + public static final String FLOAT_STRING = "f"; + public static final String STRING_STRING = "s"; + public static final String OBJECT_PATH_STRING = "o"; + public static final String SIGNATURE_STRING = "g"; + public static final String ARRAY_STRING = "a"; + public static final String VARIANT_STRING = "v"; + public static final String STRUCT_STRING = "r"; + public static final String STRUCT1_STRING = "("; + public static final String STRUCT2_STRING = ")"; + public static final String DICT_ENTRY_STRING = "e"; + public static final String DICT_ENTRY1_STRING = "{"; + public static final String DICT_ENTRY2_STRING = "}"; + + public static final byte BYTE = 'y'; + public static final byte BOOLEAN = 'b'; + public static final byte INT16 = 'n'; + public static final byte UINT16 = 'q'; + public static final byte INT32 = 'i'; + public static final byte UINT32 = 'u'; + public static final byte INT64 = 'x'; + public static final byte UINT64 = 't'; + public static final byte DOUBLE = 'd'; + public static final byte FLOAT = 'f'; + public static final byte STRING = 's'; + public static final byte OBJECT_PATH = 'o'; + public static final byte SIGNATURE = 'g'; + public static final byte ARRAY = 'a'; + public static final byte VARIANT = 'v'; + public static final byte STRUCT = 'r'; + public static final byte STRUCT1 = '('; + public static final byte STRUCT2 = ')'; + public static final byte DICT_ENTRY = 'e'; + public static final byte DICT_ENTRY1 = '{'; + public static final byte DICT_ENTRY2 = '}'; + } + + /** + * Keep a static reference to each size of padding array to prevent allocation. + */ + private static byte[][] padding; + + static { + padding = new byte[][]{ + null, + new byte[1], + new byte[2], + new byte[3], + new byte[4], + new byte[5], + new byte[6], + new byte[7]}; + } + + /** + * Steps to increment the buffer array. + */ + private static final int BUFFERINCREMENT = 20; + + private boolean big; + protected byte[][] wiredata; + protected long bytecounter; + protected Map headers; + protected static long globalserial = 0; + protected long serial; + protected byte type; + protected byte flags; + protected byte protover; + private Object[] args; + private byte[] body; + private long bodylen = 0; + private int preallocated = 0; + private int paofs = 0; + private byte[] pabuf; + private int bufferuse = 0; + + /** + * Returns the name of the given header field. + */ + public static String getHeaderFieldName(byte field) { + switch (field) { + case HeaderField.PATH: + return "Path"; + case HeaderField.INTERFACE: + return "Interface"; + case HeaderField.MEMBER: + return "Member"; + case HeaderField.ERROR_NAME: + return "Error Name"; + case HeaderField.REPLY_SERIAL: + return "Reply Serial"; + case HeaderField.DESTINATION: + return "Destination"; + case HeaderField.SENDER: + return "Sender"; + case HeaderField.SIGNATURE: + return "Signature"; + default: + return "Invalid"; + } + } + + /** + * Create a message; only to be called by sub-classes. + * + * @param endian The endianness to create the message. + * @param type The message type. + * @param flags Any message flags. + */ + protected Message(byte endian, byte type, byte flags) throws DBusException { + wiredata = new byte[BUFFERINCREMENT][]; + headers = new HashMap(); + big = (Endian.BIG == endian); + bytecounter = 0; + synchronized (Message.class) { + serial = ++globalserial; + } + if (Debug.debug) Debug.print(Debug.DEBUG, "Creating message with serial " + serial); + this.type = type; + this.flags = flags; + preallocate(4); + append("yyyy", endian, type, flags, Message.PROTOCOL); + } + + /** + * Create a blank message. Only to be used when calling populate. + */ + protected Message() { + wiredata = new byte[BUFFERINCREMENT][]; + headers = new HashMap(); + bytecounter = 0; + } + + /** + * Create a message from wire-format data. + * + * @param msg D-Bus serialized data of type yyyuu + * @param headers D-Bus serialized data of type a(yv) + * @param body D-Bus serialized data of the signature defined in headers. + */ + @SuppressWarnings("unchecked") + void populate(byte[] msg, byte[] headers, byte[] body) throws DBusException { + big = (msg[0] == Endian.BIG); + type = msg[1]; + flags = msg[2]; + protover = msg[3]; + wiredata[0] = msg; + wiredata[1] = headers; + wiredata[2] = body; + this.body = body; + bufferuse = 3; + bodylen = ((Number) extract(Message.ArgumentType.UINT32_STRING, msg, 4)[0]).longValue(); + serial = ((Number) extract(Message.ArgumentType.UINT32_STRING, msg, 8)[0]).longValue(); + bytecounter = msg.length + headers.length + body.length; + if (Debug.debug) Debug.print(Debug.VERBOSE, headers); + Object[] hs = extract("a(yv)", headers, 0); + if (Debug.debug) Debug.print(Debug.VERBOSE, Arrays.deepToString(hs)); + for (Object o : (Vector) hs[0]) { + this.headers.put((Byte) ((Object[]) o)[0], ((Variant) ((Object[]) o)[1]).getValue()); + } + } + + /** + * Create a buffer of num bytes. + * Data is copied to this rather than added to the buffer list. + */ + private void preallocate(int num) { + preallocated = 0; + pabuf = new byte[num]; + appendBytes(pabuf); + preallocated = num; + paofs = 0; + } + + /** + * Ensures there are enough free buffers. + * + * @param num number of free buffers to create. + */ + private void ensureBuffers(int num) { + int increase = num - wiredata.length + bufferuse; + if (increase > 0) { + if (increase < BUFFERINCREMENT) increase = BUFFERINCREMENT; + if (Debug.debug) Debug.print(Debug.VERBOSE, "Resizing " + bufferuse); + byte[][] temp = new byte[wiredata.length + increase][]; + System.arraycopy(wiredata, 0, temp, 0, wiredata.length); + wiredata = temp; + } + } + + /** + * Appends a buffer to the buffer list. + */ + protected void appendBytes(byte[] buf) { + if (null == buf) return; + if (preallocated > 0) { + if (paofs + buf.length > pabuf.length) + throw new ArrayIndexOutOfBoundsException(MessageFormat.format(getString("arrayOutOfBounds"), new Object[]{paofs, pabuf.length, buf.length})); + System.arraycopy(buf, 0, pabuf, paofs, buf.length); + paofs += buf.length; + preallocated -= buf.length; + } else { + if (bufferuse == wiredata.length) { + if (Debug.debug) Debug.print(Debug.VERBOSE, "Resizing " + bufferuse); + byte[][] temp = new byte[wiredata.length + BUFFERINCREMENT][]; + System.arraycopy(wiredata, 0, temp, 0, wiredata.length); + wiredata = temp; + } + wiredata[bufferuse++] = buf; + bytecounter += buf.length; + } + } + + /** + * Appends a byte to the buffer list. + */ + protected void appendByte(byte b) { + if (preallocated > 0) { + pabuf[paofs++] = b; + preallocated--; + } else { + if (bufferuse == wiredata.length) { + if (Debug.debug) Debug.print(Debug.VERBOSE, "Resizing " + bufferuse); + byte[][] temp = new byte[wiredata.length + BUFFERINCREMENT][]; + System.arraycopy(wiredata, 0, temp, 0, wiredata.length); + wiredata = temp; + } + wiredata[bufferuse++] = new byte[]{b}; + bytecounter++; + } + } + + /** + * Demarshalls an integer of a given width from a buffer. + * Endianness is determined from the format of the message. + * + * @param buf The buffer to demarshall from. + * @param ofs The offset to demarshall from. + * @param width The byte-width of the int. + */ + public long demarshallint(byte[] buf, int ofs, int width) { + return big ? demarshallintBig(buf, ofs, width) : demarshallintLittle(buf, ofs, width); + } + + /** + * Demarshalls an integer of a given width from a buffer. + * + * @param buf The buffer to demarshall from. + * @param ofs The offset to demarshall from. + * @param endian The endianness to use in demarshalling. + * @param width The byte-width of the int. + */ + public static long demarshallint(byte[] buf, int ofs, byte endian, int width) { + return endian == Endian.BIG ? demarshallintBig(buf, ofs, width) : demarshallintLittle(buf, ofs, width); + } + + /** + * Demarshalls an integer of a given width from a buffer using big-endian format. + * + * @param buf The buffer to demarshall from. + * @param ofs The offset to demarshall from. + * @param width The byte-width of the int. + */ + public static long demarshallintBig(byte[] buf, int ofs, int width) { + long l = 0; + for (int i = 0; i < width; i++) { + l <<= 8; + l |= (buf[ofs + i] & 0xFF); + } + return l; + } + + /** + * Demarshalls an integer of a given width from a buffer using little-endian format. + * + * @param buf The buffer to demarshall from. + * @param ofs The offset to demarshall from. + * @param width The byte-width of the int. + */ + public static long demarshallintLittle(byte[] buf, int ofs, int width) { + long l = 0; + for (int i = (width - 1); i >= 0; i--) { + l <<= 8; + l |= (buf[ofs + i] & 0xFF); + } + return l; + } + + /** + * Marshalls an integer of a given width and appends it to the message. + * Endianness is determined from the message. + * + * @param l The integer to marshall. + * @param width The byte-width of the int. + */ + public void appendint(long l, int width) { + byte[] buf = new byte[width]; + marshallint(l, buf, 0, width); + appendBytes(buf); + } + + /** + * Marshalls an integer of a given width into a buffer. + * Endianness is determined from the message. + * + * @param l The integer to marshall. + * @param buf The buffer to marshall to. + * @param ofs The offset to marshall to. + * @param width The byte-width of the int. + */ + public void marshallint(long l, byte[] buf, int ofs, int width) { + if (big) marshallintBig(l, buf, ofs, width); + else marshallintLittle(l, buf, ofs, width); + if (Debug.debug) Debug.print(Debug.VERBOSE, "Marshalled int " + l + " to " + Hexdump.toHex(buf, ofs, width)); + } + + /** + * Marshalls an integer of a given width into a buffer using big-endian format. + * + * @param l The integer to marshall. + * @param buf The buffer to marshall to. + * @param ofs The offset to marshall to. + * @param width The byte-width of the int. + */ + public static void marshallintBig(long l, byte[] buf, int ofs, int width) { + for (int i = (width - 1); i >= 0; i--) { + buf[i + ofs] = (byte) (l & 0xFF); + l >>= 8; + } + } + + /** + * Marshalls an integer of a given width into a buffer using little-endian format. + * + * @param l The integer to marshall. + * @param buf The buffer to demarshall to. + * @param ofs The offset to demarshall to. + * @param width The byte-width of the int. + */ + public static void marshallintLittle(long l, byte[] buf, int ofs, int width) { + for (int i = 0; i < width; i++) { + buf[i + ofs] = (byte) (l & 0xFF); + l >>= 8; + } + } + + public byte[][] getWireData() { + return wiredata; + } + + /** + * Formats the message in a human-readable format. + */ + public String toString() { + StringBuffer sb = new StringBuffer(); + sb.append(getClass().getSimpleName()); + sb.append('('); + sb.append(flags); + sb.append(','); + sb.append(serial); + sb.append(')'); + sb.append(' '); + sb.append('{'); + sb.append(' '); + if (headers.size() == 0) + sb.append('}'); + else { + for (Byte field : headers.keySet()) { + sb.append(getHeaderFieldName(field)); + sb.append('='); + sb.append('>'); + sb.append(headers.get(field).toString()); + sb.append(','); + sb.append(' '); + } + sb.setCharAt(sb.length() - 2, ' '); + sb.setCharAt(sb.length() - 1, '}'); + } + sb.append(' '); + sb.append('{'); + sb.append(' '); + Object[] args = null; + try { + args = getParameters(); + } catch (DBusException DBe) { + if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, DBe); + } + if (null == args || 0 == args.length) + sb.append('}'); + else { + for (Object o : args) { + if (o instanceof Object[]) + sb.append(Arrays.deepToString((Object[]) o)); + else if (o instanceof byte[]) + sb.append(Arrays.toString((byte[]) o)); + else if (o instanceof int[]) + sb.append(Arrays.toString((int[]) o)); + else if (o instanceof short[]) + sb.append(Arrays.toString((short[]) o)); + else if (o instanceof long[]) + sb.append(Arrays.toString((long[]) o)); + else if (o instanceof boolean[]) + sb.append(Arrays.toString((boolean[]) o)); + else if (o instanceof double[]) + sb.append(Arrays.toString((double[]) o)); + else if (o instanceof float[]) + sb.append(Arrays.toString((float[]) o)); + else + sb.append(o.toString()); + sb.append(','); + sb.append(' '); + } + sb.setCharAt(sb.length() - 2, ' '); + sb.setCharAt(sb.length() - 1, '}'); + } + return sb.toString(); + } + + /** + * Returns the value of the header field of a given field. + * + * @param type The field to return. + * @return The value of the field or null if unset. + */ + public Object getHeader(byte type) { + return headers.get(type); + } + + /** + * Appends a value to the message. + * The type of the value is read from a D-Bus signature and used to marshall + * the value. + * + * @param sigb A buffer of the D-Bus signature. + * @param sigofs The offset into the signature corresponding to this value. + * @param data The value to marshall. + * @return The offset into the signature of the end of this value's type. + */ + @SuppressWarnings("unchecked") + private int appendone(byte[] sigb, int sigofs, Object data) throws DBusException { + try { + int i = sigofs; + if (Debug.debug) Debug.print(Debug.VERBOSE, (Object) bytecounter); + if (Debug.debug) Debug.print(Debug.VERBOSE, "Appending type: " + ((char) sigb[i]) + " value: " + data); + + // pad to the alignment of this type. + pad(sigb[i]); + switch (sigb[i]) { + case ArgumentType.BYTE: + appendByte(((Number) data).byteValue()); + break; + case ArgumentType.BOOLEAN: + appendint(((Boolean) data).booleanValue() ? 1 : 0, 4); + break; + case ArgumentType.DOUBLE: + long l = Double.doubleToLongBits(((Number) data).doubleValue()); + appendint(l, 8); + break; + case ArgumentType.FLOAT: + int rf = Float.floatToIntBits(((Number) data).floatValue()); + appendint(rf, 4); + break; + case ArgumentType.UINT32: + appendint(((Number) data).longValue(), 4); + break; + case ArgumentType.INT64: + appendint(((Number) data).longValue(), 8); + break; + case ArgumentType.UINT64: + if (big) { + appendint(((UInt64) data).top(), 4); + appendint(((UInt64) data).bottom(), 4); + } else { + appendint(((UInt64) data).bottom(), 4); + appendint(((UInt64) data).top(), 4); + } + break; + case ArgumentType.INT32: + appendint(((Number) data).intValue(), 4); + break; + case ArgumentType.UINT16: + appendint(((Number) data).intValue(), 2); + break; + case ArgumentType.INT16: + appendint(((Number) data).shortValue(), 2); + break; + case ArgumentType.STRING: + case ArgumentType.OBJECT_PATH: + // Strings are marshalled as a UInt32 with the length, + // followed by the String, followed by a null byte. + String payload = data.toString(); + byte[] payloadbytes = null; + try { + payloadbytes = payload.getBytes("UTF-8"); + } catch (UnsupportedEncodingException UEe) { + if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(UEe); + throw new DBusException(getString("utf8NotSupported")); + } + if (Debug.debug) Debug.print(Debug.VERBOSE, "Appending String of length " + payloadbytes.length); + appendint(payloadbytes.length, 4); + appendBytes(payloadbytes); + appendBytes(padding[1]); + //pad(ArgumentType.STRING);? do we need this? + break; + case ArgumentType.SIGNATURE: + // Signatures are marshalled as a byte with the length, + // followed by the String, followed by a null byte. + // Signatures are generally short, so preallocate the array + // for the string, length and null byte. + if (data instanceof Type[]) + payload = Marshalling.getDBusType((Type[]) data); + else + payload = (String) data; + byte[] pbytes = payload.getBytes(); + preallocate(2 + pbytes.length); + appendByte((byte) pbytes.length); + appendBytes(pbytes); + appendByte((byte) 0); + break; + case ArgumentType.ARRAY: + // Arrays are given as a UInt32 for the length in bytes, + // padding to the element alignment, then elements in + // order. The length is the length from the end of the + // initial padding to the end of the last element. + if (Debug.debug) { + if (data instanceof Object[]) + Debug.print(Debug.VERBOSE, "Appending array: " + Arrays.deepToString((Object[]) data)); + } + + byte[] alen = new byte[4]; + appendBytes(alen); + pad(sigb[++i]); + long c = bytecounter; + + // optimise primatives + if (data.getClass().isArray() && + data.getClass().getComponentType().isPrimitive()) { + byte[] primbuf; + int algn = getAlignment(sigb[i]); + int len = Array.getLength(data); + switch (sigb[i]) { + case ArgumentType.BYTE: + primbuf = (byte[]) data; + break; + case ArgumentType.INT16: + case ArgumentType.INT32: + case ArgumentType.INT64: + primbuf = new byte[len * algn]; + for (int j = 0, k = 0; j < len; j++, k += algn) + marshallint(Array.getLong(data, j), primbuf, k, algn); + break; + case ArgumentType.BOOLEAN: + primbuf = new byte[len * algn]; + for (int j = 0, k = 0; j < len; j++, k += algn) + marshallint(Array.getBoolean(data, j) ? 1 : 0, primbuf, k, algn); + break; + case ArgumentType.DOUBLE: + primbuf = new byte[len * algn]; + if (data instanceof float[]) + for (int j = 0, k = 0; j < len; j++, k += algn) + marshallint(Double.doubleToRawLongBits(((float[]) data)[j]), + primbuf, k, algn); + else + for (int j = 0, k = 0; j < len; j++, k += algn) + marshallint(Double.doubleToRawLongBits(((double[]) data)[j]), + primbuf, k, algn); + break; + case ArgumentType.FLOAT: + primbuf = new byte[len * algn]; + for (int j = 0, k = 0; j < len; j++, k += algn) + marshallint( + Float.floatToRawIntBits(((float[]) data)[j]), + primbuf, k, algn); + break; + default: + throw new MarshallingException(getString("arraySentAsNonPrimitive")); + } + appendBytes(primbuf); + } else if (data instanceof List) { + Object[] contents = ((List) data).toArray(); + int diff = i; + ensureBuffers(contents.length * 4); + for (Object o : contents) + diff = appendone(sigb, i, o); + i = diff; + } else if (data instanceof Map) { + int diff = i; + ensureBuffers(((Map) data).size() * 6); + for (Map.Entry o : ((Map) data).entrySet()) + diff = appendone(sigb, i, o); + if (i == diff) { + // advance the type parser even on 0-size arrays. + Vector temp = new Vector(); + byte[] temp2 = new byte[sigb.length - diff]; + System.arraycopy(sigb, diff, temp2, 0, temp2.length); + String temp3 = new String(temp2); + int temp4 = Marshalling.getJavaType(temp3, temp, 1); + diff += temp4; + } + i = diff; + } else { + Object[] contents = (Object[]) data; + ensureBuffers(contents.length * 4); + int diff = i; + for (Object o : contents) + diff = appendone(sigb, i, o); + i = diff; + } + if (Debug.debug) + Debug.print(Debug.VERBOSE, "start: " + c + " end: " + bytecounter + " length: " + (bytecounter - c)); + marshallint(bytecounter - c, alen, 0, 4); + break; + case ArgumentType.STRUCT1: + // Structs are aligned to 8 bytes + // and simply contain each element marshalled in order + Object[] contents; + if (data instanceof Container) + contents = ((Container) data).getParameters(); + else + contents = (Object[]) data; + ensureBuffers(contents.length * 4); + int j = 0; + for (i++; sigb[i] != ArgumentType.STRUCT2; i++) + i = appendone(sigb, i, contents[j++]); + break; + case ArgumentType.DICT_ENTRY1: + // Dict entries are the same as structs. + if (data instanceof Map.Entry) { + i++; + i = appendone(sigb, i, ((Map.Entry) data).getKey()); + i++; + i = appendone(sigb, i, ((Map.Entry) data).getValue()); + i++; + } else { + contents = (Object[]) data; + j = 0; + for (i++; sigb[i] != ArgumentType.DICT_ENTRY2; i++) + i = appendone(sigb, i, contents[j++]); + } + break; + case ArgumentType.VARIANT: + // Variants are marshalled as a signature + // followed by the value. + if (data instanceof Variant) { + Variant var = (Variant) data; + appendone(new byte[]{ArgumentType.SIGNATURE}, 0, var.getSig()); + appendone((var.getSig()).getBytes(), 0, var.getValue()); + } else if (data instanceof Object[]) { + contents = (Object[]) data; + appendone(new byte[]{ArgumentType.SIGNATURE}, 0, contents[0]); + appendone(((String) contents[0]).getBytes(), 0, contents[1]); + } else { + String sig = Marshalling.getDBusType(data.getClass())[0]; + appendone(new byte[]{ArgumentType.SIGNATURE}, 0, sig); + appendone((sig).getBytes(), 0, data); + } + break; + } + return i; + } catch (ClassCastException CCe) { + if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, CCe); + throw new MarshallingException(MessageFormat.format(getString("unconvertableType"), new Object[]{data.getClass().getName(), sigb[sigofs]})); + } + } + + /** + * Pad the message to the proper alignment for the given type. + */ + public void pad(byte type) { + if (Debug.debug) Debug.print(Debug.VERBOSE, "padding for " + (char) type); + int a = getAlignment(type); + if (Debug.debug) Debug.print(Debug.VERBOSE, preallocated + " " + paofs + " " + bytecounter + " " + a); + int b = (int) ((bytecounter - preallocated) % a); + if (0 == b) return; + a = (a - b); + if (preallocated > 0) { + paofs += a; + preallocated -= a; + } else + appendBytes(padding[a]); + if (Debug.debug) Debug.print(Debug.VERBOSE, preallocated + " " + paofs + " " + bytecounter + " " + a); + } + + /** + * Return the alignment for a given type. + */ + public static int getAlignment(byte type) { + switch (type) { + case 2: + case ArgumentType.INT16: + case ArgumentType.UINT16: + return 2; + case 4: + case ArgumentType.BOOLEAN: + case ArgumentType.FLOAT: + case ArgumentType.INT32: + case ArgumentType.UINT32: + case ArgumentType.STRING: + case ArgumentType.OBJECT_PATH: + case ArgumentType.ARRAY: + return 4; + case 8: + case ArgumentType.INT64: + case ArgumentType.UINT64: + case ArgumentType.DOUBLE: + case ArgumentType.STRUCT: + case ArgumentType.DICT_ENTRY: + case ArgumentType.STRUCT1: + case ArgumentType.DICT_ENTRY1: + case ArgumentType.STRUCT2: + case ArgumentType.DICT_ENTRY2: + return 8; + case 1: + case ArgumentType.BYTE: + case ArgumentType.SIGNATURE: + case ArgumentType.VARIANT: + default: + return 1; + } + } + + /** + * Append a series of values to the message. + * + * @param sig The signature(s) of the value(s). + * @param data The value(s). + */ + public void append(String sig, Object... data) throws DBusException { + if (Debug.debug) Debug.print(Debug.DEBUG, "Appending sig: " + sig + " data: " + Arrays.deepToString(data)); + byte[] sigb = sig.getBytes(); + int j = 0; + for (int i = 0; i < sigb.length; i++) { + if (Debug.debug) Debug.print(Debug.VERBOSE, "Appending item: " + i + " " + ((char) sigb[i]) + " " + j); + i = appendone(sigb, i, data[j++]); + } + } + + /** + * Align a counter to the given type. + * + * @param current The current counter. + * @param type The type to align to. + * @return The new, aligned, counter. + */ + public int align(int current, byte type) { + if (Debug.debug) Debug.print(Debug.VERBOSE, "aligning to " + (char) type); + int a = getAlignment(type); + if (0 == (current % a)) return current; + return current + (a - (current % a)); + } + + /** + * Demarshall one value from a buffer. + * + * @param sigb A buffer of the D-Bus signature. + * @param buf The buffer to demarshall from. + * @param ofs An array of two ints, the offset into the signature buffer + * and the offset into the data buffer. These values will be + * updated to the start of the next value ofter demarshalling. + * @param contained converts nested arrays to Lists + * @return The demarshalled value. + */ + private Object extractone(byte[] sigb, byte[] buf, int[] ofs, boolean contained) throws DBusException { + if (Debug.debug) + Debug.print(Debug.VERBOSE, "Extracting type: " + ((char) sigb[ofs[0]]) + " from offset " + ofs[1]); + Object rv = null; + ofs[1] = align(ofs[1], sigb[ofs[0]]); + switch (sigb[ofs[0]]) { + case ArgumentType.BYTE: + rv = buf[ofs[1]++]; + break; + case ArgumentType.UINT32: + rv = new UInt32(demarshallint(buf, ofs[1], 4)); + ofs[1] += 4; + break; + case ArgumentType.INT32: + rv = (int) demarshallint(buf, ofs[1], 4); + ofs[1] += 4; + break; + case ArgumentType.INT16: + rv = (short) demarshallint(buf, ofs[1], 2); + ofs[1] += 2; + break; + case ArgumentType.UINT16: + rv = new UInt16((int) demarshallint(buf, ofs[1], 2)); + ofs[1] += 2; + break; + case ArgumentType.INT64: + rv = demarshallint(buf, ofs[1], 8); + ofs[1] += 8; + break; + case ArgumentType.UINT64: + long top; + long bottom; + if (big) { + top = demarshallint(buf, ofs[1], 4); + ofs[1] += 4; + bottom = demarshallint(buf, ofs[1], 4); + } else { + bottom = demarshallint(buf, ofs[1], 4); + ofs[1] += 4; + top = demarshallint(buf, ofs[1], 4); + } + rv = new UInt64(top, bottom); + ofs[1] += 4; + break; + case ArgumentType.DOUBLE: + long l = demarshallint(buf, ofs[1], 8); + ofs[1] += 8; + rv = Double.longBitsToDouble(l); + break; + case ArgumentType.FLOAT: + int rf = (int) demarshallint(buf, ofs[1], 4); + ofs[1] += 4; + rv = Float.intBitsToFloat(rf); + break; + case ArgumentType.BOOLEAN: + rf = (int) demarshallint(buf, ofs[1], 4); + ofs[1] += 4; + rv = (1 == rf) ? Boolean.TRUE : Boolean.FALSE; + break; + case ArgumentType.ARRAY: + long size = demarshallint(buf, ofs[1], 4); + if (Debug.debug) Debug.print(Debug.VERBOSE, "Reading array of size: " + size); + ofs[1] += 4; + byte algn = (byte) getAlignment(sigb[++ofs[0]]); + ofs[1] = align(ofs[1], sigb[ofs[0]]); + int length = (int) (size / algn); + if (length > DBusConnection.MAX_ARRAY_LENGTH) + throw new MarshallingException(getString("arrayMustNotExceed") + DBusConnection.MAX_ARRAY_LENGTH); + // optimise primatives + switch (sigb[ofs[0]]) { + case ArgumentType.BYTE: + rv = new byte[length]; + System.arraycopy(buf, ofs[1], rv, 0, length); + ofs[1] += size; + break; + case ArgumentType.INT16: + rv = new short[length]; + for (int j = 0; j < length; j++, ofs[1] += algn) + ((short[]) rv)[j] = (short) demarshallint(buf, ofs[1], algn); + break; + case ArgumentType.INT32: + rv = new int[length]; + for (int j = 0; j < length; j++, ofs[1] += algn) + ((int[]) rv)[j] = (int) demarshallint(buf, ofs[1], algn); + break; + case ArgumentType.INT64: + rv = new long[length]; + for (int j = 0; j < length; j++, ofs[1] += algn) + ((long[]) rv)[j] = demarshallint(buf, ofs[1], algn); + break; + case ArgumentType.BOOLEAN: + rv = new boolean[length]; + for (int j = 0; j < length; j++, ofs[1] += algn) + ((boolean[]) rv)[j] = (1 == demarshallint(buf, ofs[1], algn)); + break; + case ArgumentType.FLOAT: + rv = new float[length]; + for (int j = 0; j < length; j++, ofs[1] += algn) + ((float[]) rv)[j] = + Float.intBitsToFloat((int) demarshallint(buf, ofs[1], algn)); + break; + case ArgumentType.DOUBLE: + rv = new double[length]; + for (int j = 0; j < length; j++, ofs[1] += algn) + ((double[]) rv)[j] = + Double.longBitsToDouble(demarshallint(buf, ofs[1], algn)); + break; + case ArgumentType.DICT_ENTRY1: + if (0 == size) { + // advance the type parser even on 0-size arrays. + Vector temp = new Vector(); + byte[] temp2 = new byte[sigb.length - ofs[0]]; + System.arraycopy(sigb, ofs[0], temp2, 0, temp2.length); + String temp3 = new String(temp2); + // ofs[0] gets incremented anyway. Leave one character on the stack + int temp4 = Marshalling.getJavaType(temp3, temp, 1) - 1; + ofs[0] += temp4; + if (Debug.debug) + Debug.print(Debug.VERBOSE, "Aligned type: " + temp3 + " " + temp4 + " " + ofs[0]); + } + int ofssave = ofs[0]; + long end = ofs[1] + size; + Vector entries = new Vector(); + while (ofs[1] < end) { + ofs[0] = ofssave; + entries.add((Object[]) extractone(sigb, buf, ofs, true)); + } + rv = new DBusMap(entries.toArray(new Object[0][])); + break; + default: + if (0 == size) { + // advance the type parser even on 0-size arrays. + Vector temp = new Vector(); + byte[] temp2 = new byte[sigb.length - ofs[0]]; + System.arraycopy(sigb, ofs[0], temp2, 0, temp2.length); + String temp3 = new String(temp2); + // ofs[0] gets incremented anyway. Leave one character on the stack + int temp4 = Marshalling.getJavaType(temp3, temp, 1) - 1; + ofs[0] += temp4; + if (Debug.debug) + Debug.print(Debug.VERBOSE, "Aligned type: " + temp3 + " " + temp4 + " " + ofs[0]); + } + ofssave = ofs[0]; + end = ofs[1] + size; + Vector contents = new Vector(); + while (ofs[1] < end) { + ofs[0] = ofssave; + contents.add(extractone(sigb, buf, ofs, true)); + } + rv = contents; + } + if (contained && !(rv instanceof List) && !(rv instanceof Map)) + rv = ArrayFrob.listify(rv); + break; + case ArgumentType.STRUCT1: + Vector contents = new Vector(); + while (sigb[++ofs[0]] != ArgumentType.STRUCT2) + contents.add(extractone(sigb, buf, ofs, true)); + rv = contents.toArray(); + break; + case ArgumentType.DICT_ENTRY1: + Object[] decontents = new Object[2]; + if (Debug.debug) + Debug.print(Debug.VERBOSE, "Extracting Dict Entry (" + Hexdump.toAscii(sigb, ofs[0], sigb.length - ofs[0]) + ") from: " + Hexdump.toHex(buf, ofs[1], buf.length - ofs[1])); + ofs[0]++; + decontents[0] = extractone(sigb, buf, ofs, true); + ofs[0]++; + decontents[1] = extractone(sigb, buf, ofs, true); + ofs[0]++; + rv = decontents; + break; + case ArgumentType.VARIANT: + int[] newofs = new int[]{0, ofs[1]}; + String sig = (String) extract(ArgumentType.SIGNATURE_STRING, buf, newofs)[0]; + newofs[0] = 0; + rv = new Variant(extract(sig, buf, newofs)[0], sig); + ofs[1] = newofs[1]; + break; + case ArgumentType.STRING: + length = (int) demarshallint(buf, ofs[1], 4); + ofs[1] += 4; + try { + rv = new String(buf, ofs[1], length, "UTF-8"); + } catch (UnsupportedEncodingException UEe) { + if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(UEe); + throw new DBusException(getString("utf8NotSupported")); + } + ofs[1] += length + 1; + break; + case ArgumentType.OBJECT_PATH: + length = (int) demarshallint(buf, ofs[1], 4); + ofs[1] += 4; + rv = new ObjectPath(getSource(), new String(buf, ofs[1], length)); + ofs[1] += length + 1; + break; + case ArgumentType.SIGNATURE: + length = (buf[ofs[1]++] & 0xFF); + rv = new String(buf, ofs[1], length); + ofs[1] += length + 1; + break; + default: + throw new UnknownTypeCodeException(sigb[ofs[0]]); + } + if (Debug.debug) if (rv instanceof Object[]) + Debug.print(Debug.VERBOSE, "Extracted: " + Arrays.deepToString((Object[]) rv) + " (now at " + ofs[1] + ")"); + else + Debug.print(Debug.VERBOSE, "Extracted: " + rv + " (now at " + ofs[1] + ")"); + return rv; + } + + /** + * Demarshall values from a buffer. + * + * @param sig The D-Bus signature(s) of the value(s). + * @param buf The buffer to demarshall from. + * @param ofs The offset into the data buffer to start. + * @return The demarshalled value(s). + */ + public Object[] extract(String sig, byte[] buf, int ofs) throws DBusException { + return extract(sig, buf, new int[]{0, ofs}); + } + + /** + * Demarshall values from a buffer. + * + * @param sig The D-Bus signature(s) of the value(s). + * @param buf The buffer to demarshall from. + * @param ofs An array of two ints, the offset into the signature + * and the offset into the data buffer. These values will be + * updated to the start of the next value ofter demarshalling. + * @return The demarshalled value(s). + */ + public Object[] extract(String sig, byte[] buf, int[] ofs) throws DBusException { + if (Debug.debug) + Debug.print(Debug.VERBOSE, "extract(" + sig + ",#" + buf.length + ", {" + ofs[0] + "," + ofs[1] + "}"); + Vector rv = new Vector(); + byte[] sigb = sig.getBytes(); + for (int[] i = ofs; i[0] < sigb.length; i[0]++) { + rv.add(extractone(sigb, buf, i, false)); + } + return rv.toArray(); + } + + /** + * Returns the Bus ID that sent the message. + */ + public String getSource() { + return (String) headers.get(HeaderField.SENDER); + } + + /** + * Returns the destination of the message. + */ + public String getDestination() { + return (String) headers.get(HeaderField.DESTINATION); + } + + /** + * Returns the interface of the message. + */ + public String getInterface() { + return (String) headers.get(HeaderField.INTERFACE); + } + + /** + * Returns the object path of the message. + */ + public String getPath() { + Object o = headers.get(HeaderField.PATH); + if (null == o) return null; + return o.toString(); + } + + /** + * Returns the member name or error name this message represents. + */ + public String getName() { + if (this instanceof Error) + return (String) headers.get(HeaderField.ERROR_NAME); + else + return (String) headers.get(HeaderField.MEMBER); + } + + /** + * Returns the dbus signature of the parameters. + */ + public String getSig() { + return (String) headers.get(HeaderField.SIGNATURE); + } + + /** + * Returns the message flags. + */ + public int getFlags() { + return flags; + } + + /** + * Returns the message serial ID (unique for this connection) + * + * @return the message serial. + */ + public long getSerial() { + return serial; + } + + /** + * If this is a reply to a message, this returns its serial. + * + * @return The reply serial, or 0 if it is not a reply. + */ + public long getReplySerial() { + Number l = (Number) headers.get(HeaderField.REPLY_SERIAL); + if (null == l) return 0; + return l.longValue(); + } + + /** + * Parses and returns the parameters to this message as an Object array. + */ + public Object[] getParameters() throws DBusException { + if (null == args && null != body) { + String sig = (String) headers.get(HeaderField.SIGNATURE); + if (null != sig && 0 != body.length) { + args = extract(sig, body, 0); + } else args = new Object[0]; + } + return args; + } + + protected void setArgs(Object[] args) { + this.args = args; + } + + /** + * Warning, do not use this method unless you really know what you are doing. + */ + public void setSource(String source) throws DBusException { + if (null != body) { + wiredata = new byte[BUFFERINCREMENT][]; + bufferuse = 0; + bytecounter = 0; + preallocate(12); + append("yyyyuu", big ? Endian.BIG : Endian.LITTLE, type, flags, protover, bodylen, serial); + headers.put(HeaderField.SENDER, source); + Object[][] newhead = new Object[headers.size()][]; + int i = 0; + for (Byte b : headers.keySet()) { + newhead[i] = new Object[2]; + newhead[i][0] = b; + newhead[i][1] = headers.get(b); + i++; + } + append("a(yv)", (Object) newhead); + pad((byte) 8); + appendBytes(body); + } + } +} diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/MessageReader.java b/federation/sssd/src/main/java/org/freedesktop/dbus/MessageReader.java new file mode 100644 index 0000000000..4dbea003c9 --- /dev/null +++ b/federation/sssd/src/main/java/org/freedesktop/dbus/MessageReader.java @@ -0,0 +1,194 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; + +import cx.ath.matthew.debug.Debug; +import cx.ath.matthew.utils.Hexdump; +import org.freedesktop.dbus.exceptions.DBusException; +import org.freedesktop.dbus.exceptions.MessageProtocolVersionException; +import org.freedesktop.dbus.exceptions.MessageTypeException; + +import java.io.BufferedInputStream; +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; +import java.net.SocketTimeoutException; +import java.text.MessageFormat; + +import static org.freedesktop.dbus.Gettext.getString; + +public class MessageReader { + private InputStream in; + private byte[] buf = null; + private byte[] tbuf = null; + private byte[] header = null; + private byte[] body = null; + private int[] len = new int[4]; + + public MessageReader(InputStream in) { + this.in = new BufferedInputStream(in); + } + + public Message readMessage() throws IOException, DBusException { + int rv; + /* Read the 12 byte fixed header, retrying as neccessary */ + if (null == buf) { + buf = new byte[12]; + len[0] = 0; + } + if (len[0] < 12) { + try { + rv = in.read(buf, len[0], 12 - len[0]); + } catch (SocketTimeoutException STe) { + return null; + } + if (-1 == rv) throw new EOFException(getString("transportReturnedEOF")); + len[0] += rv; + } + if (len[0] == 0) return null; + if (len[0] < 12) { + if (Debug.debug) Debug.print(Debug.DEBUG, "Only got " + len[0] + " of 12 bytes of header"); + return null; + } + + /* Parse the details from the header */ + byte endian = buf[0]; + byte type = buf[1]; + byte protover = buf[3]; + if (protover > Message.PROTOCOL) { + buf = null; + throw new MessageProtocolVersionException(MessageFormat.format(getString("protocolVersionUnsupported"), new Object[]{protover})); + } + + /* Read the length of the variable header */ + if (null == tbuf) { + tbuf = new byte[4]; + len[1] = 0; + } + if (len[1] < 4) { + try { + rv = in.read(tbuf, len[1], 4 - len[1]); + } catch (SocketTimeoutException STe) { + return null; + } + if (-1 == rv) throw new EOFException(getString("transportReturnedEOF")); + len[1] += rv; + } + if (len[1] < 4) { + if (Debug.debug) Debug.print(Debug.DEBUG, "Only got " + len[1] + " of 4 bytes of header"); + return null; + } + + /* Parse the variable header length */ + int headerlen = 0; + if (null == header) { + headerlen = (int) Message.demarshallint(tbuf, 0, endian, 4); + if (0 != headerlen % 8) + headerlen += 8 - (headerlen % 8); + } else + headerlen = header.length - 8; + + /* Read the variable header */ + if (null == header) { + header = new byte[headerlen + 8]; + System.arraycopy(tbuf, 0, header, 0, 4); + len[2] = 0; + } + if (len[2] < headerlen) { + try { + rv = in.read(header, 8 + len[2], headerlen - len[2]); + } catch (SocketTimeoutException STe) { + return null; + } + if (-1 == rv) throw new EOFException(getString("transportReturnedEOF")); + len[2] += rv; + } + if (len[2] < headerlen) { + if (Debug.debug) Debug.print(Debug.DEBUG, "Only got " + len[2] + " of " + headerlen + " bytes of header"); + return null; + } + + /* Read the body */ + int bodylen = 0; + if (null == body) bodylen = (int) Message.demarshallint(buf, 4, endian, 4); + if (null == body) { + body = new byte[bodylen]; + len[3] = 0; + } + if (len[3] < body.length) { + try { + rv = in.read(body, len[3], body.length - len[3]); + } catch (SocketTimeoutException STe) { + return null; + } + if (-1 == rv) throw new EOFException(getString("transportReturnedEOF")); + len[3] += rv; + } + if (len[3] < body.length) { + if (Debug.debug) Debug.print(Debug.DEBUG, "Only got " + len[3] + " of " + body.length + " bytes of body"); + return null; + } + + Message m; + switch (type) { + case Message.MessageType.METHOD_CALL: + m = new MethodCall(); + break; + case Message.MessageType.METHOD_RETURN: + m = new MethodReturn(); + break; + case Message.MessageType.SIGNAL: + m = new DBusSignal(); + break; + case Message.MessageType.ERROR: + m = new Error(); + break; + default: + throw new MessageTypeException(MessageFormat.format(getString("messageTypeUnsupported"), new Object[]{type})); + } + if (Debug.debug) { + Debug.print(Debug.VERBOSE, Hexdump.format(buf)); + Debug.print(Debug.VERBOSE, Hexdump.format(tbuf)); + Debug.print(Debug.VERBOSE, Hexdump.format(header)); + Debug.print(Debug.VERBOSE, Hexdump.format(body)); + } + try { + m.populate(buf, header, body); + } catch (DBusException DBe) { + if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, DBe); + buf = null; + tbuf = null; + body = null; + header = null; + throw DBe; + } catch (RuntimeException Re) { + if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, Re); + buf = null; + tbuf = null; + body = null; + header = null; + throw Re; + } + if (Debug.debug) { + Debug.print(Debug.INFO, "=> " + m); + } + buf = null; + tbuf = null; + body = null; + header = null; + return m; + } + + public void close() throws IOException { + if (Debug.debug) Debug.print(Debug.INFO, "Closing Message Reader"); + in.close(); + } +} diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/MessageWriter.java b/federation/sssd/src/main/java/org/freedesktop/dbus/MessageWriter.java new file mode 100644 index 0000000000..45e8cb75f5 --- /dev/null +++ b/federation/sssd/src/main/java/org/freedesktop/dbus/MessageWriter.java @@ -0,0 +1,67 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; + +import cx.ath.matthew.debug.Debug; +import cx.ath.matthew.unix.USOutputStream; +import cx.ath.matthew.utils.Hexdump; + +import java.io.BufferedOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +public class MessageWriter { + private OutputStream out; + private boolean isunix; + + public MessageWriter(OutputStream out) { + this.out = out; + this.isunix = false; + try { + if (out instanceof USOutputStream) + this.isunix = true; + } catch (Throwable t) { + } + if (!this.isunix) + this.out = new BufferedOutputStream(this.out); + } + + public void writeMessage(Message m) throws IOException { + if (Debug.debug) { + Debug.print(Debug.INFO, "<= " + m); + } + if (null == m) return; + if (null == m.getWireData()) { + if (Debug.debug) Debug.print(Debug.WARN, "Message " + m + " wire-data was null!"); + return; + } + if (isunix) { + if (Debug.debug) { + Debug.print(Debug.DEBUG, "Writing all " + m.getWireData().length + " buffers simultaneously to Unix Socket"); + for (byte[] buf : m.getWireData()) + Debug.print(Debug.VERBOSE, "(" + buf + "):" + (null == buf ? "" : Hexdump.format(buf))); + } + ((USOutputStream) out).write(m.getWireData()); + } else + for (byte[] buf : m.getWireData()) { + if (Debug.debug) + Debug.print(Debug.VERBOSE, "(" + buf + "):" + (null == buf ? "" : Hexdump.format(buf))); + if (null == buf) break; + out.write(buf); + } + out.flush(); + } + + public void close() throws IOException { + if (Debug.debug) Debug.print(Debug.INFO, "Closing Message Writer"); + out.close(); + } +} diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/MethodCall.java b/federation/sssd/src/main/java/org/freedesktop/dbus/MethodCall.java new file mode 100644 index 0000000000..72242ccb9a --- /dev/null +++ b/federation/sssd/src/main/java/org/freedesktop/dbus/MethodCall.java @@ -0,0 +1,137 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; + +import cx.ath.matthew.debug.Debug; +import cx.ath.matthew.utils.Hexdump; +import org.freedesktop.dbus.exceptions.DBusException; +import org.freedesktop.dbus.exceptions.MessageFormatException; + +import java.util.Vector; + +import static org.freedesktop.dbus.Gettext.getString; + +public class MethodCall extends Message { + MethodCall() { + } + + public MethodCall(String dest, String path, String iface, String member, byte flags, String sig, Object... args) throws DBusException { + this(null, dest, path, iface, member, flags, sig, args); + } + + public MethodCall(String source, String dest, String path, String iface, String member, byte flags, String sig, Object... args) throws DBusException { + super(Message.Endian.BIG, Message.MessageType.METHOD_CALL, flags); + + if (null == member || null == path) + throw new MessageFormatException(getString("missingDestinationPathFunction")); + headers.put(Message.HeaderField.PATH, path); + headers.put(Message.HeaderField.MEMBER, member); + + Vector hargs = new Vector(); + + hargs.add(new Object[]{Message.HeaderField.PATH, new Object[]{ArgumentType.OBJECT_PATH_STRING, path}}); + + if (null != source) { + headers.put(Message.HeaderField.SENDER, source); + hargs.add(new Object[]{Message.HeaderField.SENDER, new Object[]{ArgumentType.STRING_STRING, source}}); + } + + if (null != dest) { + headers.put(Message.HeaderField.DESTINATION, dest); + hargs.add(new Object[]{Message.HeaderField.DESTINATION, new Object[]{ArgumentType.STRING_STRING, dest}}); + } + + if (null != iface) { + hargs.add(new Object[]{Message.HeaderField.INTERFACE, new Object[]{ArgumentType.STRING_STRING, iface}}); + headers.put(Message.HeaderField.INTERFACE, iface); + } + + hargs.add(new Object[]{Message.HeaderField.MEMBER, new Object[]{ArgumentType.STRING_STRING, member}}); + + if (null != sig) { + if (Debug.debug) Debug.print(Debug.DEBUG, "Appending arguments with signature: " + sig); + hargs.add(new Object[]{Message.HeaderField.SIGNATURE, new Object[]{ArgumentType.SIGNATURE_STRING, sig}}); + headers.put(Message.HeaderField.SIGNATURE, sig); + setArgs(args); + } + + byte[] blen = new byte[4]; + appendBytes(blen); + append("ua(yv)", serial, hargs.toArray()); + pad((byte) 8); + + long c = bytecounter; + if (null != sig) append(sig, args); + if (Debug.debug) + Debug.print(Debug.DEBUG, "Appended body, type: " + sig + " start: " + c + " end: " + bytecounter + " size: " + (bytecounter - c)); + marshallint(bytecounter - c, blen, 0, 4); + if (Debug.debug) Debug.print("marshalled size (" + blen + "): " + Hexdump.format(blen)); + } + + private static long REPLY_WAIT_TIMEOUT = 20000; + + /** + * Set the default timeout for method calls. + * Default is 20s. + * + * @param timeout New timeout in ms. + */ + public static void setDefaultTimeout(long timeout) { + REPLY_WAIT_TIMEOUT = timeout; + } + + Message reply = null; + + public synchronized boolean hasReply() { + return null != reply; + } + + /** + * Block (if neccessary) for a reply. + * + * @param timeout The length of time to block before timing out (ms). + * @return The reply to this MethodCall, or null if a timeout happens. + */ + public synchronized Message getReply(long timeout) { + if (Debug.debug) Debug.print(Debug.VERBOSE, "Blocking on " + this); + if (null != reply) return reply; + try { + wait(timeout); + return reply; + } catch (InterruptedException Ie) { + return reply; + } + } + + /** + * Block (if neccessary) for a reply. + * Default timeout is 20s, or can be configured with setDefaultTimeout() + * + * @return The reply to this MethodCall, or null if a timeout happens. + */ + public synchronized Message getReply() { + if (Debug.debug) Debug.print(Debug.VERBOSE, "Blocking on " + this); + if (null != reply) return reply; + try { + wait(REPLY_WAIT_TIMEOUT); + return reply; + } catch (InterruptedException Ie) { + return reply; + } + } + + protected synchronized void setReply(Message reply) { + if (Debug.debug) Debug.print(Debug.VERBOSE, "Setting reply to " + this + " to " + reply); + this.reply = reply; + notifyAll(); + } + +} diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/MethodReturn.java b/federation/sssd/src/main/java/org/freedesktop/dbus/MethodReturn.java new file mode 100644 index 0000000000..35f4d0d745 --- /dev/null +++ b/federation/sssd/src/main/java/org/freedesktop/dbus/MethodReturn.java @@ -0,0 +1,77 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; + +import org.freedesktop.dbus.exceptions.DBusException; + +import java.util.Vector; + +public class MethodReturn extends Message { + MethodReturn() { + } + + public MethodReturn(String dest, long replyserial, String sig, Object... args) throws DBusException { + this(null, dest, replyserial, sig, args); + } + + public MethodReturn(String source, String dest, long replyserial, String sig, Object... args) throws DBusException { + super(Message.Endian.BIG, Message.MessageType.METHOD_RETURN, (byte) 0); + + headers.put(Message.HeaderField.REPLY_SERIAL, replyserial); + + Vector hargs = new Vector(); + hargs.add(new Object[]{Message.HeaderField.REPLY_SERIAL, new Object[]{ArgumentType.UINT32_STRING, replyserial}}); + + if (null != source) { + headers.put(Message.HeaderField.SENDER, source); + hargs.add(new Object[]{Message.HeaderField.SENDER, new Object[]{ArgumentType.STRING_STRING, source}}); + } + + if (null != dest) { + headers.put(Message.HeaderField.DESTINATION, dest); + hargs.add(new Object[]{Message.HeaderField.DESTINATION, new Object[]{ArgumentType.STRING_STRING, dest}}); + } + + if (null != sig) { + hargs.add(new Object[]{Message.HeaderField.SIGNATURE, new Object[]{ArgumentType.SIGNATURE_STRING, sig}}); + headers.put(Message.HeaderField.SIGNATURE, sig); + setArgs(args); + } + + byte[] blen = new byte[4]; + appendBytes(blen); + append("ua(yv)", serial, hargs.toArray()); + pad((byte) 8); + + long c = bytecounter; + if (null != sig) append(sig, args); + marshallint(bytecounter - c, blen, 0, 4); + } + + public MethodReturn(MethodCall mc, String sig, Object... args) throws DBusException { + this(null, mc, sig, args); + } + + public MethodReturn(String source, MethodCall mc, String sig, Object... args) throws DBusException { + this(source, mc.getSource(), mc.getSerial(), sig, args); + this.call = mc; + } + + MethodCall call; + + public MethodCall getCall() { + return call; + } + + protected void setCall(MethodCall call) { + this.call = call; + } +} diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/MethodTuple.java b/federation/sssd/src/main/java/org/freedesktop/dbus/MethodTuple.java new file mode 100644 index 0000000000..d135714edd --- /dev/null +++ b/federation/sssd/src/main/java/org/freedesktop/dbus/MethodTuple.java @@ -0,0 +1,37 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; + +import cx.ath.matthew.debug.Debug; + +class MethodTuple { + String name; + String sig; + + public MethodTuple(String name, String sig) { + this.name = name; + if (null != sig) + this.sig = sig; + else + this.sig = ""; + if (Debug.debug) Debug.print(Debug.VERBOSE, "new MethodTuple(" + this.name + ", " + this.sig + ")"); + } + + public boolean equals(Object o) { + return o.getClass().equals(MethodTuple.class) + && ((MethodTuple) o).name.equals(this.name) + && ((MethodTuple) o).sig.equals(this.sig); + } + + public int hashCode() { + return name.hashCode() + sig.hashCode(); + } +} diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/ObjectPath.java b/federation/sssd/src/main/java/org/freedesktop/dbus/ObjectPath.java new file mode 100644 index 0000000000..5ea11583c3 --- /dev/null +++ b/federation/sssd/src/main/java/org/freedesktop/dbus/ObjectPath.java @@ -0,0 +1,22 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; + +class ObjectPath extends Path { + public String source; + + // public DBusConnection conn; + public ObjectPath(String source, String path/*, DBusConnection conn*/) { + super(path); + this.source = source; + // this.conn = conn; + } +} diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/ObjectTree.java b/federation/sssd/src/main/java/org/freedesktop/dbus/ObjectTree.java new file mode 100644 index 0000000000..0332e6a33f --- /dev/null +++ b/federation/sssd/src/main/java/org/freedesktop/dbus/ObjectTree.java @@ -0,0 +1,158 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; + +import cx.ath.matthew.debug.Debug; + +import java.util.regex.Pattern; + +/** + * Keeps track of the exported objects for introspection data + */ +class ObjectTree { + class TreeNode { + String name; + ExportedObject object; + String data; + TreeNode right; + TreeNode down; + + public TreeNode(String name) { + this.name = name; + } + + public TreeNode(String name, ExportedObject object, String data) { + this.name = name; + this.object = object; + this.data = data; + } + } + + private TreeNode root; + + public ObjectTree() { + root = new TreeNode(""); + } + + public static final Pattern slashpattern = Pattern.compile("/"); + + private TreeNode recursiveFind(TreeNode current, String path) { + if ("/".equals(path)) return current; + String[] elements = path.split("/", 2); + // this is us or a parent node + if (path.startsWith(current.name)) { + // this is us + if (path.equals(current.name)) { + return current; + } + // recurse down + else { + if (current.down == null) + return null; + else return recursiveFind(current.down, elements[1]); + } + } else if (current.right == null) { + return null; + } else if (0 > current.right.name.compareTo(elements[0])) { + return null; + } + // recurse right + else { + return recursiveFind(current.right, path); + } + } + + private TreeNode recursiveAdd(TreeNode current, String path, ExportedObject object, String data) { + String[] elements = slashpattern.split(path, 2); + // this is us or a parent node + if (path.startsWith(current.name)) { + // this is us + if (1 == elements.length || "".equals(elements[1])) { + current.object = object; + current.data = data; + } + // recurse down + else { + if (current.down == null) { + String[] el = elements[1].split("/", 2); + current.down = new TreeNode(el[0]); + } + current.down = recursiveAdd(current.down, elements[1], object, data); + } + } + // need to create a new sub-tree on the end + else if (current.right == null) { + current.right = new TreeNode(elements[0]); + current.right = recursiveAdd(current.right, path, object, data); + } + // need to insert here + else if (0 > current.right.name.compareTo(elements[0])) { + TreeNode t = new TreeNode(elements[0]); + t.right = current.right; + current.right = t; + current.right = recursiveAdd(current.right, path, object, data); + } + // recurse right + else { + current.right = recursiveAdd(current.right, path, object, data); + } + return current; + } + + public void add(String path, ExportedObject object, String data) { + if (Debug.debug) Debug.print(Debug.DEBUG, "Adding " + path + " to object tree"); + root = recursiveAdd(root, path, object, data); + } + + public void remove(String path) { + if (Debug.debug) Debug.print(Debug.DEBUG, "Removing " + path + " from object tree"); + TreeNode t = recursiveFind(root, path); + t.object = null; + t.data = null; + } + + public String Introspect(String path) { + TreeNode t = recursiveFind(root, path); + if (null == t) return null; + StringBuilder sb = new StringBuilder(); + sb.append("\n"); + if (null != t.data) sb.append(t.data); + t = t.down; + while (null != t) { + sb.append("\n"); + t = t.right; + } + sb.append(""); + return sb.toString(); + } + + private String recursivePrint(TreeNode current) { + String s = ""; + if (null != current) { + s += current.name; + if (null != current.object) + s += "*"; + if (null != current.down) + s += "/{" + recursivePrint(current.down) + "}"; + if (null != current.right) + s += ", " + recursivePrint(current.right); + } + return s; + } + + public String toString() { + return recursivePrint(root); + } +} diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/Path.java b/federation/sssd/src/main/java/org/freedesktop/dbus/Path.java new file mode 100644 index 0000000000..91a5a6d716 --- /dev/null +++ b/federation/sssd/src/main/java/org/freedesktop/dbus/Path.java @@ -0,0 +1,39 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; + +public class Path implements Comparable { + protected String path; + + public Path(String path) { + this.path = path; + } + + public String getPath() { + return path; + } + + public String toString() { + return path; + } + + public boolean equals(Object other) { + return (other instanceof Path) && path.equals(((Path) other).path); + } + + public int hashCode() { + return path.hashCode(); + } + + public int compareTo(Path that) { + return path.compareTo(that.path); + } +} diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/Position.java b/federation/sssd/src/main/java/org/freedesktop/dbus/Position.java new file mode 100644 index 0000000000..ed0bb11652 --- /dev/null +++ b/federation/sssd/src/main/java/org/freedesktop/dbus/Position.java @@ -0,0 +1,29 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Position annotation, to annotate Struct fields + * to be sent over DBus. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface Position { + /** + * The order of this field in the Struct. + */ + int value(); +} diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/RemoteInvocationHandler.java b/federation/sssd/src/main/java/org/freedesktop/dbus/RemoteInvocationHandler.java new file mode 100644 index 0000000000..f6b7867859 --- /dev/null +++ b/federation/sssd/src/main/java/org/freedesktop/dbus/RemoteInvocationHandler.java @@ -0,0 +1,187 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; + +import cx.ath.matthew.debug.Debug; +import org.freedesktop.DBus; +import org.freedesktop.dbus.exceptions.DBusException; +import org.freedesktop.dbus.exceptions.DBusExecutionException; +import org.freedesktop.dbus.exceptions.NotConnected; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.lang.reflect.Type; +import java.text.MessageFormat; +import java.util.Arrays; + +import static org.freedesktop.dbus.Gettext.getString; + +class RemoteInvocationHandler implements InvocationHandler { + public static final int CALL_TYPE_SYNC = 0; + public static final int CALL_TYPE_ASYNC = 1; + public static final int CALL_TYPE_CALLBACK = 2; + + public static Object convertRV(String sig, Object[] rp, Method m, AbstractConnection conn) throws DBusException { + Class c = m.getReturnType(); + + if (null == rp) { + if (null == c || Void.TYPE.equals(c)) return null; + else throw new DBusExecutionException(getString("voidReturnType")); + } else { + try { + if (Debug.debug) + Debug.print(Debug.VERBOSE, "Converting return parameters from " + Arrays.deepToString(rp) + " to type " + m.getGenericReturnType()); + rp = Marshalling.deSerializeParameters(rp, + new Type[]{m.getGenericReturnType()}, conn); + } catch (Exception e) { + if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, e); + throw new DBusExecutionException(MessageFormat.format(getString("invalidReturnType"), new Object[]{e.getMessage()})); + } + } + + switch (rp.length) { + case 0: + if (null == c || Void.TYPE.equals(c)) + return null; + else throw new DBusExecutionException(getString("voidReturnType")); + case 1: + return rp[0]; + default: + + // check we are meant to return multiple values + if (!Tuple.class.isAssignableFrom(c)) + throw new DBusExecutionException(getString("tupleReturnType")); + + Constructor cons = c.getConstructors()[0]; + try { + return cons.newInstance(rp); + } catch (Exception e) { + if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, e); + throw new DBusException(e.getMessage()); + } + } + } + + @SuppressWarnings("unchecked") + public static Object executeRemoteMethod(RemoteObject ro, Method m, AbstractConnection conn, int syncmethod, CallbackHandler callback, Object... args) throws DBusExecutionException { + Type[] ts = m.getGenericParameterTypes(); + String sig = null; + if (ts.length > 0) try { + sig = Marshalling.getDBusType(ts); + args = Marshalling.convertParameters(args, ts, conn); + } catch (DBusException DBe) { + throw new DBusExecutionException(getString("contructDBusTypeFailure") + DBe.getMessage()); + } + MethodCall call; + byte flags = 0; + if (!ro.autostart) flags |= Message.Flags.NO_AUTO_START; + if (syncmethod == CALL_TYPE_ASYNC) flags |= Message.Flags.ASYNC; + if (m.isAnnotationPresent(DBus.Method.NoReply.class)) flags |= Message.Flags.NO_REPLY_EXPECTED; + try { + String name; + if (m.isAnnotationPresent(DBusMemberName.class)) + name = m.getAnnotation(DBusMemberName.class).value(); + else + name = m.getName(); + if (null == ro.iface) + call = new MethodCall(ro.busname, ro.objectpath, null, name, flags, sig, args); + else { + if (null != ro.iface.getAnnotation(DBusInterfaceName.class)) { + call = new MethodCall(ro.busname, ro.objectpath, ro.iface.getAnnotation(DBusInterfaceName.class).value(), name, flags, sig, args); + } else + call = new MethodCall(ro.busname, ro.objectpath, AbstractConnection.dollar_pattern.matcher(ro.iface.getName()).replaceAll("."), name, flags, sig, args); + } + } catch (DBusException DBe) { + if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, DBe); + throw new DBusExecutionException(getString("constructOutgoingMethodCallFailure") + DBe.getMessage()); + } + if (null == conn.outgoing) throw new NotConnected(getString("notConnected")); + + switch (syncmethod) { + case CALL_TYPE_ASYNC: + conn.queueOutgoing(call); + return new DBusAsyncReply(call, m, conn); + case CALL_TYPE_CALLBACK: + synchronized (conn.pendingCallbacks) { + if (Debug.debug) Debug.print(Debug.VERBOSE, "Queueing Callback " + callback + " for " + call); + conn.pendingCallbacks.put(call, callback); + conn.pendingCallbackReplys.put(call, new DBusAsyncReply(call, m, conn)); + } + conn.queueOutgoing(call); + return null; + case CALL_TYPE_SYNC: + conn.queueOutgoing(call); + break; + } + + // get reply + if (m.isAnnotationPresent(DBus.Method.NoReply.class)) return null; + + Message reply = call.getReply(); + if (null == reply) throw new DBus.Error.NoReply(getString("notReplyWithSpecifiedTime")); + + if (reply instanceof Error) + ((Error) reply).throwException(); + + try { + return convertRV(reply.getSig(), reply.getParameters(), m, conn); + } catch (DBusException e) { + if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, e); + throw new DBusExecutionException(e.getMessage()); + } + } + + AbstractConnection conn; + RemoteObject remote; + + public RemoteInvocationHandler(AbstractConnection conn, RemoteObject remote) { + this.remote = remote; + this.conn = conn; + } + + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + if (method.getName().equals("isRemote")) return true; + else if (method.getName().equals("clone")) return null; + else if (method.getName().equals("equals")) { + try { + if (1 == args.length) + return new Boolean(remote.equals(((RemoteInvocationHandler) Proxy.getInvocationHandler(args[0])).remote)); + } catch (IllegalArgumentException IAe) { + return Boolean.FALSE; + } + } else if (method.getName().equals("finalize")) return null; + else if (method.getName().equals("getClass")) return DBusInterface.class; + else if (method.getName().equals("hashCode")) return remote.hashCode(); + else if (method.getName().equals("notify")) { + remote.notify(); + return null; + } else if (method.getName().equals("notifyAll")) { + remote.notifyAll(); + return null; + } else if (method.getName().equals("wait")) { + if (0 == args.length) remote.wait(); + else if (1 == args.length + && args[0] instanceof Long) remote.wait((Long) args[0]); + else if (2 == args.length + && args[0] instanceof Long + && args[1] instanceof Integer) + remote.wait((Long) args[0], (Integer) args[1]); + if (args.length <= 2) + return null; + } else if (method.getName().equals("toString")) + return remote.toString(); + + return executeRemoteMethod(remote, method, conn, CALL_TYPE_SYNC, null, args); + } +} + diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/RemoteObject.java b/federation/sssd/src/main/java/org/freedesktop/dbus/RemoteObject.java new file mode 100644 index 0000000000..c157d12b1d --- /dev/null +++ b/federation/sssd/src/main/java/org/freedesktop/dbus/RemoteObject.java @@ -0,0 +1,67 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; + +class RemoteObject { + String busname; + String objectpath; + Class iface; + boolean autostart; + + public RemoteObject(String busname, String objectpath, Class iface, boolean autostart) { + this.busname = busname; + this.objectpath = objectpath; + this.iface = iface; + this.autostart = autostart; + } + + public boolean equals(Object o) { + if (!(o instanceof RemoteObject)) return false; + RemoteObject them = (RemoteObject) o; + + if (!them.objectpath.equals(this.objectpath)) return false; + + if (null == this.busname && null != them.busname) return false; + if (null != this.busname && null == them.busname) return false; + if (null != them.busname && !them.busname.equals(this.busname)) return false; + + if (null == this.iface && null != them.iface) return false; + if (null != this.iface && null == them.iface) return false; + if (null != them.iface && !them.iface.equals(this.iface)) return false; + + return true; + } + + public int hashCode() { + return (null == busname ? 0 : busname.hashCode()) + objectpath.hashCode() + + (null == iface ? 0 : iface.hashCode()); + } + + public boolean autoStarting() { + return autostart; + } + + public String getBusName() { + return busname; + } + + public String getObjectPath() { + return objectpath; + } + + public Class getInterface() { + return iface; + } + + public String toString() { + return busname + ":" + objectpath + ":" + iface; + } +} diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/SignalTuple.java b/federation/sssd/src/main/java/org/freedesktop/dbus/SignalTuple.java new file mode 100644 index 0000000000..29ec987d2b --- /dev/null +++ b/federation/sssd/src/main/java/org/freedesktop/dbus/SignalTuple.java @@ -0,0 +1,51 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; + +class SignalTuple { + String type; + String name; + String object; + String source; + + public SignalTuple(String type, String name, String object, String source) { + this.type = type; + this.name = name; + this.object = object; + this.source = source; + } + + public boolean equals(Object o) { + if (!(o instanceof SignalTuple)) return false; + SignalTuple other = (SignalTuple) o; + if (null == this.type && null != other.type) return false; + if (null != this.type && !this.type.equals(other.type)) return false; + if (null == this.name && null != other.name) return false; + if (null != this.name && !this.name.equals(other.name)) return false; + if (null == this.object && null != other.object) return false; + if (null != this.object && !this.object.equals(other.object)) return false; + if (null == this.source && null != other.source) return false; + if (null != this.source && !this.source.equals(other.source)) return false; + return true; + } + + public int hashCode() { + return (null == type ? 0 : type.hashCode()) + + (null == name ? 0 : name.hashCode()) + + (null == source ? 0 : source.hashCode()) + + (null == object ? 0 : object.hashCode()); + } + + public String toString() { + return "SignalTuple(" + type + "," + name + "," + object + "," + source + ")"; + } +} + diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/StrongReference.java b/federation/sssd/src/main/java/org/freedesktop/dbus/StrongReference.java new file mode 100644 index 0000000000..d5358eca5d --- /dev/null +++ b/federation/sssd/src/main/java/org/freedesktop/dbus/StrongReference.java @@ -0,0 +1,42 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; + +import java.lang.ref.WeakReference; + +/** + * An alternative to a WeakReference when you don't want + * that behaviour. + */ +public class StrongReference extends WeakReference { + T referant; + + public StrongReference(T referant) { + super(referant); + this.referant = referant; + } + + public void clear() { + referant = null; + } + + public boolean enqueue() { + return false; + } + + public T get() { + return referant; + } + + public boolean isEnqueued() { + return false; + } +} diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/Struct.java b/federation/sssd/src/main/java/org/freedesktop/dbus/Struct.java new file mode 100644 index 0000000000..18667e58ec --- /dev/null +++ b/federation/sssd/src/main/java/org/freedesktop/dbus/Struct.java @@ -0,0 +1,23 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; + +/** + * This class should be extended to create Structs. + * Any such class may be sent over DBus to a method which takes a Struct. + * All fields in the Struct which you wish to be serialized and sent to the + * remote method should be annotated with the org.freedesktop.dbus.Position + * annotation, in the order they should appear in to Struct to DBus. + */ +public abstract class Struct extends Container { + public Struct() { + } +} diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/Transport.java b/federation/sssd/src/main/java/org/freedesktop/dbus/Transport.java new file mode 100644 index 0000000000..1745bcfec2 --- /dev/null +++ b/federation/sssd/src/main/java/org/freedesktop/dbus/Transport.java @@ -0,0 +1,835 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; + +import cx.ath.matthew.debug.Debug; +import cx.ath.matthew.unix.UnixSocket; +import cx.ath.matthew.unix.UnixSocketAddress; +import cx.ath.matthew.utils.Hexdump; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.PrintWriter; +import java.lang.reflect.Method; +import java.net.InetSocketAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.text.Collator; +import java.text.ParseException; +import java.util.Arrays; +import java.util.Random; +import java.util.Vector; + +import static org.freedesktop.dbus.Gettext.getString; + +public class Transport { + public static class SASL { + public static class Command { + private int command; + private int mechs; + private String data; + private String response; + + public Command() { + } + + public Command(String s) throws IOException { + String[] ss = s.split(" "); + if (Debug.debug) Debug.print(Debug.VERBOSE, "Creating command from: " + Arrays.toString(ss)); + if (0 == col.compare(ss[0], "OK")) { + command = COMMAND_OK; + data = ss[1]; + } else if (0 == col.compare(ss[0], "AUTH")) { + command = COMMAND_AUTH; + if (ss.length > 1) { + if (0 == col.compare(ss[1], "EXTERNAL")) + mechs = AUTH_EXTERNAL; + else if (0 == col.compare(ss[1], "DBUS_COOKIE_SHA1")) + mechs = AUTH_SHA; + else if (0 == col.compare(ss[1], "ANONYMOUS")) + mechs = AUTH_ANON; + } + if (ss.length > 2) + data = ss[2]; + } else if (0 == col.compare(ss[0], "DATA")) { + command = COMMAND_DATA; + data = ss[1]; + } else if (0 == col.compare(ss[0], "REJECTED")) { + command = COMMAND_REJECTED; + for (int i = 1; i < ss.length; i++) + if (0 == col.compare(ss[i], "EXTERNAL")) + mechs |= AUTH_EXTERNAL; + else if (0 == col.compare(ss[i], "DBUS_COOKIE_SHA1")) + mechs |= AUTH_SHA; + else if (0 == col.compare(ss[i], "ANONYMOUS")) + mechs |= AUTH_ANON; + } else if (0 == col.compare(ss[0], "BEGIN")) { + command = COMMAND_BEGIN; + } else if (0 == col.compare(ss[0], "CANCEL")) { + command = COMMAND_CANCEL; + } else if (0 == col.compare(ss[0], "ERROR")) { + command = COMMAND_ERROR; + data = ss[1]; + } else { + throw new IOException(getString("invalidCommand") + ss[0]); + } + if (Debug.debug) Debug.print(Debug.VERBOSE, "Created command: " + this); + } + + public int getCommand() { + return command; + } + + public int getMechs() { + return mechs; + } + + public String getData() { + return data; + } + + public String getResponse() { + return response; + } + + public void setResponse(String s) { + response = s; + } + + public String toString() { + return "Command(" + command + ", " + mechs + ", " + data + ", " + null + ")"; + } + } + + private static Collator col = Collator.getInstance(); + + static { + col.setDecomposition(Collator.FULL_DECOMPOSITION); + col.setStrength(Collator.PRIMARY); + } + + public static final int LOCK_TIMEOUT = 1000; + public static final int NEW_KEY_TIMEOUT_SECONDS = 60 * 5; + public static final int EXPIRE_KEYS_TIMEOUT_SECONDS = NEW_KEY_TIMEOUT_SECONDS + (60 * 2); + public static final int MAX_TIME_TRAVEL_SECONDS = 60 * 5; + public static final int COOKIE_TIMEOUT = 240; + public static final String COOKIE_CONTEXT = "org_freedesktop_java"; + + private String findCookie(String context, String ID) throws IOException { + String homedir = System.getProperty("user.home"); + File f = new File(homedir + "/.dbus-keyrings/" + context); + BufferedReader r = new BufferedReader(new InputStreamReader(new FileInputStream(f))); + String s = null; + String cookie = null; + long now = System.currentTimeMillis() / 1000; + while (null != (s = r.readLine())) { + String[] line = s.split(" "); + long timestamp = Long.parseLong(line[1]); + if (line[0].equals(ID) && (!(timestamp < 0 || + (now + MAX_TIME_TRAVEL_SECONDS) < timestamp || + (now - EXPIRE_KEYS_TIMEOUT_SECONDS) > timestamp))) { + cookie = line[2]; + break; + } + } + r.close(); + return cookie; + } + + private void addCookie(String context, String ID, long timestamp, String cookie) throws IOException { + String homedir = System.getProperty("user.home"); + File keydir = new File(homedir + "/.dbus-keyrings/"); + File cookiefile = new File(homedir + "/.dbus-keyrings/" + context); + File lock = new File(homedir + "/.dbus-keyrings/" + context + ".lock"); + File temp = new File(homedir + "/.dbus-keyrings/" + context + ".temp"); + + // ensure directory exists + if (!keydir.exists()) keydir.mkdirs(); + + // acquire lock + long start = System.currentTimeMillis(); + while (!lock.createNewFile() && LOCK_TIMEOUT > (System.currentTimeMillis() - start)) ; + + // read old file + Vector lines = new Vector(); + if (cookiefile.exists()) { + BufferedReader r = new BufferedReader(new InputStreamReader(new FileInputStream(cookiefile))); + String s = null; + while (null != (s = r.readLine())) { + String[] line = s.split(" "); + long time = Long.parseLong(line[1]); + // expire stale cookies + if ((timestamp - time) < COOKIE_TIMEOUT) + lines.add(s); + } + r.close(); + } + + // add cookie + lines.add(ID + " " + timestamp + " " + cookie); + + // write temp file + PrintWriter w = new PrintWriter(new FileOutputStream(temp)); + for (String l : lines) + w.println(l); + w.close(); + + // atomically move to old file + if (!temp.renameTo(cookiefile)) { + cookiefile.delete(); + temp.renameTo(cookiefile); + } + + // remove lock + lock.delete(); + } + + /** + * Takes the string, encodes it as hex and then turns it into a string again. + * No, I don't know why either. + */ + private String stupidlyEncode(String data) { + return Hexdump.toHex(data.getBytes()).replaceAll(" ", ""); + } + + private String stupidlyEncode(byte[] data) { + return Hexdump.toHex(data).replaceAll(" ", ""); + } + + private byte getNibble(char c) { + switch (c) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + return (byte) (c - '0'); + case 'A': + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': + return (byte) (c - 'A' + 10); + case 'a': + case 'b': + case 'c': + case 'd': + case 'e': + case 'f': + return (byte) (c - 'a' + 10); + default: + return 0; + } + } + + private String stupidlyDecode(String data) { + char[] cs = new char[data.length()]; + char[] res = new char[cs.length / 2]; + data.getChars(0, data.length(), cs, 0); + for (int i = 0, j = 0; j < res.length; i += 2, j++) { + int b = 0; + b |= getNibble(cs[i]) << 4; + b |= getNibble(cs[i + 1]); + res[j] = (char) b; + } + return new String(res); + } + + public static final int MODE_SERVER = 1; + public static final int MODE_CLIENT = 2; + + public static final int AUTH_NONE = 0; + public static final int AUTH_EXTERNAL = 1; + public static final int AUTH_SHA = 2; + public static final int AUTH_ANON = 4; + + public static final int COMMAND_AUTH = 1; + public static final int COMMAND_DATA = 2; + public static final int COMMAND_REJECTED = 3; + public static final int COMMAND_OK = 4; + public static final int COMMAND_BEGIN = 5; + public static final int COMMAND_CANCEL = 6; + public static final int COMMAND_ERROR = 7; + + public static final int INITIAL_STATE = 0; + public static final int WAIT_DATA = 1; + public static final int WAIT_OK = 2; + public static final int WAIT_REJECT = 3; + public static final int WAIT_AUTH = 4; + public static final int WAIT_BEGIN = 5; + public static final int AUTHENTICATED = 6; + public static final int FAILED = 7; + + public static final int OK = 1; + public static final int CONTINUE = 2; + public static final int ERROR = 3; + public static final int REJECT = 4; + + public Command receive(InputStream s) throws IOException { + StringBuffer sb = new StringBuffer(); + top: + while (true) { + int c = s.read(); + switch (c) { + case -1: + throw new IOException("Stream unexpectedly short (broken pipe)"); + case 0: + case '\r': + continue; + case '\n': + break top; + default: + sb.append((char) c); + } + } + if (Debug.debug) Debug.print(Debug.VERBOSE, "received: " + sb); + try { + return new Command(sb.toString()); + } catch (Exception e) { + if (Debug.debug && AbstractConnection.EXCEPTION_DEBUG) Debug.print(Debug.ERR, e); + return new Command(); + } + } + + public void send(OutputStream out, int command, String... data) throws IOException { + StringBuffer sb = new StringBuffer(); + switch (command) { + case COMMAND_AUTH: + sb.append("AUTH"); + break; + case COMMAND_DATA: + sb.append("DATA"); + break; + case COMMAND_REJECTED: + sb.append("REJECTED"); + break; + case COMMAND_OK: + sb.append("OK"); + break; + case COMMAND_BEGIN: + sb.append("BEGIN"); + break; + case COMMAND_CANCEL: + sb.append("CANCEL"); + break; + case COMMAND_ERROR: + sb.append("ERROR"); + break; + default: + return; + } + for (String s : data) { + sb.append(' '); + sb.append(s); + } + sb.append('\r'); + sb.append('\n'); + if (Debug.debug) Debug.print(Debug.VERBOSE, "sending: " + sb); + out.write(sb.toString().getBytes()); + } + + public int do_challenge(int auth, Command c) throws IOException { + switch (auth) { + case AUTH_SHA: + String[] reply = stupidlyDecode(c.getData()).split(" "); + if (Debug.debug) Debug.print(Debug.VERBOSE, Arrays.toString(reply)); + if (3 != reply.length) { + if (Debug.debug) Debug.print(Debug.DEBUG, "Reply is not length 3"); + return ERROR; + } + String context = reply[0]; + String ID = reply[1]; + String serverchallenge = reply[2]; + MessageDigest md = null; + try { + md = MessageDigest.getInstance("SHA"); + } catch (NoSuchAlgorithmException NSAe) { + if (Debug.debug && AbstractConnection.EXCEPTION_DEBUG) Debug.print(Debug.ERR, NSAe); + return ERROR; + } + byte[] buf = new byte[8]; + Message.marshallintBig(System.currentTimeMillis(), buf, 0, 8); + String clientchallenge = stupidlyEncode(md.digest(buf)); + md.reset(); + long start = System.currentTimeMillis(); + String cookie = null; + while (null == cookie && (System.currentTimeMillis() - start) < LOCK_TIMEOUT) + cookie = findCookie(context, ID); + if (null == cookie) { + if (Debug.debug) + Debug.print(Debug.DEBUG, "Did not find a cookie in context " + context + " with ID " + ID); + return ERROR; + } + String response = serverchallenge + ":" + clientchallenge + ":" + cookie; + buf = md.digest(response.getBytes()); + if (Debug.debug) + Debug.print(Debug.VERBOSE, "Response: " + response + " hash: " + Hexdump.format(buf)); + response = stupidlyEncode(buf); + c.setResponse(stupidlyEncode(clientchallenge + " " + response)); + return OK; + default: + if (Debug.debug) Debug.print(Debug.DEBUG, "Not DBUS_COOKIE_SHA1 authtype."); + return ERROR; + } + } + + public String challenge = ""; + public String cookie = ""; + + public int do_response(int auth, String Uid, String kernelUid, Command c) { + MessageDigest md = null; + try { + md = MessageDigest.getInstance("SHA"); + } catch (NoSuchAlgorithmException NSAe) { + if (Debug.debug && AbstractConnection.EXCEPTION_DEBUG) Debug.print(Debug.ERR, NSAe); + return ERROR; + } + switch (auth) { + case AUTH_NONE: + switch (c.getMechs()) { + case AUTH_ANON: + return OK; + case AUTH_EXTERNAL: + if (0 == col.compare(Uid, c.getData()) && + (null == kernelUid || 0 == col.compare(Uid, kernelUid))) + return OK; + else + return ERROR; + case AUTH_SHA: + String context = COOKIE_CONTEXT; + long id = System.currentTimeMillis(); + byte[] buf = new byte[8]; + Message.marshallintBig(id, buf, 0, 8); + challenge = stupidlyEncode(md.digest(buf)); + Random r = new Random(); + r.nextBytes(buf); + cookie = stupidlyEncode(md.digest(buf)); + try { + addCookie(context, "" + id, id / 1000, cookie); + } catch (IOException IOe) { + if (Debug.debug && AbstractConnection.EXCEPTION_DEBUG) Debug.print(Debug.ERR, IOe); + } + if (Debug.debug) + Debug.print(Debug.DEBUG, "Sending challenge: " + context + ' ' + id + ' ' + challenge); + c.setResponse(stupidlyEncode(context + ' ' + id + ' ' + challenge)); + return CONTINUE; + default: + return ERROR; + } + case AUTH_SHA: + String[] response = stupidlyDecode(c.getData()).split(" "); + if (response.length < 2) return ERROR; + String cchal = response[0]; + String hash = response[1]; + String prehash = challenge + ":" + cchal + ":" + cookie; + byte[] buf = md.digest(prehash.getBytes()); + String posthash = stupidlyEncode(buf); + if (Debug.debug) + Debug.print(Debug.DEBUG, "Authenticating Hash; data=" + prehash + " remote hash=" + hash + " local hash=" + posthash); + if (0 == col.compare(posthash, hash)) + return OK; + else + return ERROR; + default: + return ERROR; + } + } + + public String[] getTypes(int types) { + switch (types) { + case AUTH_EXTERNAL: + return new String[]{"EXTERNAL"}; + case AUTH_SHA: + return new String[]{"DBUS_COOKIE_SHA1"}; + case AUTH_ANON: + return new String[]{"ANONYMOUS"}; + case AUTH_SHA + AUTH_EXTERNAL: + return new String[]{"EXTERNAL", "DBUS_COOKIE_SHA1"}; + case AUTH_SHA + AUTH_ANON: + return new String[]{"ANONYMOUS", "DBUS_COOKIE_SHA1"}; + case AUTH_EXTERNAL + AUTH_ANON: + return new String[]{"ANONYMOUS", "EXTERNAL"}; + case AUTH_EXTERNAL + AUTH_ANON + AUTH_SHA: + return new String[]{"ANONYMOUS", "EXTERNAL", "DBUS_COOKIE_SHA1"}; + default: + return new String[]{}; + } + } + + /** + * performs SASL auth on the given streams. + * Mode selects whether to run as a SASL server or client. + * Types is a bitmask of the available auth types. + * Returns true if the auth was successful and false if it failed. + */ + @SuppressWarnings("unchecked") + public boolean auth(int mode, int types, String guid, OutputStream out, InputStream in, UnixSocket us) throws IOException { + String username = System.getProperty("user.name"); + String Uid = null; + String kernelUid = null; + try { + Class c = Class.forName("com.sun.security.auth.module.UnixSystem"); + Method m = c.getMethod("getUid"); + Object o = c.newInstance(); + long uid = (Long) m.invoke(o); + Uid = stupidlyEncode("" + uid); + } catch (Exception e) { + Uid = stupidlyEncode(username); + } + Command c; + int failed = 0; + int current = 0; + int state = INITIAL_STATE; + + while (state != AUTHENTICATED && state != FAILED) { + if (Debug.debug) Debug.print(Debug.VERBOSE, "AUTH state: " + state); + switch (mode) { + case MODE_CLIENT: + switch (state) { + case INITIAL_STATE: + if (null == us) + out.write(new byte[]{0}); + else + us.sendCredentialByte((byte) 0); + send(out, COMMAND_AUTH); + state = WAIT_DATA; + break; + case WAIT_DATA: + c = receive(in); + switch (c.getCommand()) { + case COMMAND_DATA: + switch (do_challenge(current, c)) { + case CONTINUE: + send(out, COMMAND_DATA, c.getResponse()); + break; + case OK: + send(out, COMMAND_DATA, c.getResponse()); + state = WAIT_OK; + break; + case ERROR: + send(out, COMMAND_ERROR, c.getResponse()); + break; + } + break; + case COMMAND_REJECTED: + failed |= current; + int available = c.getMechs() & (~failed); + if (0 != (available & AUTH_EXTERNAL)) { + send(out, COMMAND_AUTH, "EXTERNAL", Uid); + current = AUTH_EXTERNAL; + } else if (0 != (available & AUTH_SHA)) { + send(out, COMMAND_AUTH, "DBUS_COOKIE_SHA1", Uid); + current = AUTH_SHA; + } else if (0 != (available & AUTH_ANON)) { + send(out, COMMAND_AUTH, "ANONYMOUS"); + current = AUTH_ANON; + } else state = FAILED; + break; + case COMMAND_ERROR: + send(out, COMMAND_CANCEL); + state = WAIT_REJECT; + break; + case COMMAND_OK: + send(out, COMMAND_BEGIN); + state = AUTHENTICATED; + break; + default: + send(out, COMMAND_ERROR, "Got invalid command"); + break; + } + break; + case WAIT_OK: + c = receive(in); + switch (c.getCommand()) { + case COMMAND_OK: + send(out, COMMAND_BEGIN); + state = AUTHENTICATED; + break; + case COMMAND_ERROR: + case COMMAND_DATA: + send(out, COMMAND_CANCEL); + state = WAIT_REJECT; + break; + case COMMAND_REJECTED: + failed |= current; + int available = c.getMechs() & (~failed); + state = WAIT_DATA; + if (0 != (available & AUTH_EXTERNAL)) { + send(out, COMMAND_AUTH, "EXTERNAL", Uid); + current = AUTH_EXTERNAL; + } else if (0 != (available & AUTH_SHA)) { + send(out, COMMAND_AUTH, "DBUS_COOKIE_SHA1", Uid); + current = AUTH_SHA; + } else if (0 != (available & AUTH_ANON)) { + send(out, COMMAND_AUTH, "ANONYMOUS"); + current = AUTH_ANON; + } else state = FAILED; + break; + default: + send(out, COMMAND_ERROR, "Got invalid command"); + break; + } + break; + case WAIT_REJECT: + c = receive(in); + switch (c.getCommand()) { + case COMMAND_REJECTED: + failed |= current; + int available = c.getMechs() & (~failed); + if (0 != (available & AUTH_EXTERNAL)) { + send(out, COMMAND_AUTH, "EXTERNAL", Uid); + current = AUTH_EXTERNAL; + } else if (0 != (available & AUTH_SHA)) { + send(out, COMMAND_AUTH, "DBUS_COOKIE_SHA1", Uid); + current = AUTH_SHA; + } else if (0 != (available & AUTH_ANON)) { + send(out, COMMAND_AUTH, "ANONYMOUS"); + current = AUTH_ANON; + } else state = FAILED; + break; + default: + state = FAILED; + break; + } + break; + default: + state = FAILED; + } + break; + case MODE_SERVER: + switch (state) { + case INITIAL_STATE: + byte[] buf = new byte[1]; + if (null == us) { + in.read(buf); + } else { + buf[0] = us.recvCredentialByte(); + int kuid = us.getPeerUID(); + if (kuid >= 0) + kernelUid = stupidlyEncode("" + kuid); + } + if (0 != buf[0]) state = FAILED; + else state = WAIT_AUTH; + break; + case WAIT_AUTH: + c = receive(in); + switch (c.getCommand()) { + case COMMAND_AUTH: + if (null == c.getData()) { + send(out, COMMAND_REJECTED, getTypes(types)); + } else { + switch (do_response(current, Uid, kernelUid, c)) { + case CONTINUE: + send(out, COMMAND_DATA, c.getResponse()); + current = c.getMechs(); + state = WAIT_DATA; + break; + case OK: + send(out, COMMAND_OK, guid); + state = WAIT_BEGIN; + current = 0; + break; + case REJECT: + send(out, COMMAND_REJECTED, getTypes(types)); + current = 0; + break; + } + } + break; + case COMMAND_ERROR: + send(out, COMMAND_REJECTED, getTypes(types)); + break; + case COMMAND_BEGIN: + state = FAILED; + break; + default: + send(out, COMMAND_ERROR, "Got invalid command"); + break; + } + break; + case WAIT_DATA: + c = receive(in); + switch (c.getCommand()) { + case COMMAND_DATA: + switch (do_response(current, Uid, kernelUid, c)) { + case CONTINUE: + send(out, COMMAND_DATA, c.getResponse()); + state = WAIT_DATA; + break; + case OK: + send(out, COMMAND_OK, guid); + state = WAIT_BEGIN; + current = 0; + break; + case REJECT: + send(out, COMMAND_REJECTED, getTypes(types)); + current = 0; + break; + } + break; + case COMMAND_ERROR: + case COMMAND_CANCEL: + send(out, COMMAND_REJECTED, getTypes(types)); + state = WAIT_AUTH; + break; + case COMMAND_BEGIN: + state = FAILED; + break; + default: + send(out, COMMAND_ERROR, "Got invalid command"); + break; + } + break; + case WAIT_BEGIN: + c = receive(in); + switch (c.getCommand()) { + case COMMAND_ERROR: + case COMMAND_CANCEL: + send(out, COMMAND_REJECTED, getTypes(types)); + state = WAIT_AUTH; + break; + case COMMAND_BEGIN: + state = AUTHENTICATED; + break; + default: + send(out, COMMAND_ERROR, "Got invalid command"); + break; + } + break; + default: + state = FAILED; + } + break; + default: + return false; + } + } + + return state == AUTHENTICATED; + } + } + + public MessageReader min; + public MessageWriter mout; + + public Transport() { + } + + public static String genGUID() { + Random r = new Random(); + byte[] buf = new byte[16]; + r.nextBytes(buf); + String guid = Hexdump.toHex(buf); + return guid.replaceAll(" ", ""); + } + + public Transport(BusAddress address) throws IOException { + connect(address); + } + + public Transport(String address) throws IOException, ParseException { + connect(new BusAddress(address)); + } + + public Transport(String address, int timeout) throws IOException, ParseException { + connect(new BusAddress(address), timeout); + } + + public void connect(String address) throws IOException, ParseException { + connect(new BusAddress(address), 0); + } + + public void connect(String address, int timeout) throws IOException, ParseException { + connect(new BusAddress(address), timeout); + } + + public void connect(BusAddress address) throws IOException { + connect(address, 0); + } + + public void connect(BusAddress address, int timeout) throws IOException { + if (Debug.debug) Debug.print(Debug.INFO, "Connecting to " + address); + OutputStream out = null; + InputStream in = null; + UnixSocket us = null; + Socket s = null; + int mode = 0; + int types = 0; + if ("unix".equals(address.getType())) { + types = SASL.AUTH_EXTERNAL; + mode = SASL.MODE_CLIENT; + us = new UnixSocket(); + if (null != address.getParameter("abstract")) + us.connect(new UnixSocketAddress(address.getParameter("abstract"), true)); + else if (null != address.getParameter("path")) + us.connect(new UnixSocketAddress(address.getParameter("path"), false)); + us.setPassCred(true); + in = us.getInputStream(); + out = us.getOutputStream(); + } else if ("tcp".equals(address.getType())) { + types = SASL.AUTH_SHA; + if (null != address.getParameter("listen")) { + mode = SASL.MODE_SERVER; + ServerSocket ss = new ServerSocket(); + ss.bind(new InetSocketAddress(address.getParameter("host"), Integer.parseInt(address.getParameter("port")))); + s = ss.accept(); + } else { + mode = SASL.MODE_CLIENT; + s = new Socket(); + s.connect(new InetSocketAddress(address.getParameter("host"), Integer.parseInt(address.getParameter("port")))); + } + in = s.getInputStream(); + out = s.getOutputStream(); + } else { + throw new IOException(getString("unknownAddress") + address.getType()); + } + + if (!(new SASL()).auth(mode, types, address.getParameter("guid"), out, in, us)) { + out.close(); + throw new IOException(getString("errorAuth")); + } + if (null != us) { + if (Debug.debug) Debug.print(Debug.VERBOSE, "Setting timeout to " + timeout + " on Socket"); + if (timeout == 1) + us.setBlocking(false); + else + us.setSoTimeout(timeout); + } + if (null != s) { + if (Debug.debug) Debug.print(Debug.VERBOSE, "Setting timeout to " + timeout + " on Socket"); + s.setSoTimeout(timeout); + } + mout = new MessageWriter(out); + min = new MessageReader(in); + } + + public void disconnect() throws IOException { + if (Debug.debug) Debug.print(Debug.INFO, "Disconnecting Transport"); + min.close(); + mout.close(); + } +} + + diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/Tuple.java b/federation/sssd/src/main/java/org/freedesktop/dbus/Tuple.java new file mode 100644 index 0000000000..5fc13d5fb4 --- /dev/null +++ b/federation/sssd/src/main/java/org/freedesktop/dbus/Tuple.java @@ -0,0 +1,24 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; + +/** + * This class should be extended to create Tuples. + * Any such class may be used as the return type for a method + * which returns multiple values. + * All fields in the Tuple which you wish to be serialized and sent to the + * remote method should be annotated with the org.freedesktop.dbus.Position + * annotation, in the order they should appear to DBus. + */ +public abstract class Tuple extends Container { + public Tuple() { + } +} diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/TypeSignature.java b/federation/sssd/src/main/java/org/freedesktop/dbus/TypeSignature.java new file mode 100644 index 0000000000..eaad1667ad --- /dev/null +++ b/federation/sssd/src/main/java/org/freedesktop/dbus/TypeSignature.java @@ -0,0 +1,37 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; + +import org.freedesktop.dbus.exceptions.DBusException; + +import java.lang.reflect.Type; + +public class TypeSignature { + String sig; + + public TypeSignature(String sig) { + this.sig = sig; + } + + public TypeSignature(Type[] types) throws DBusException { + StringBuffer sb = new StringBuffer(); + for (Type t : types) { + String[] ts = Marshalling.getDBusType(t); + for (String s : ts) + sb.append(s); + } + this.sig = sb.toString(); + } + + public String getSig() { + return sig; + } +} diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/UInt16.java b/federation/sssd/src/main/java/org/freedesktop/dbus/UInt16.java new file mode 100644 index 0000000000..a8f0600116 --- /dev/null +++ b/federation/sssd/src/main/java/org/freedesktop/dbus/UInt16.java @@ -0,0 +1,122 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; + +import java.text.MessageFormat; + +import static org.freedesktop.dbus.Gettext.getString; + +/** + * Class to represent 16-bit unsigned integers. + */ +@SuppressWarnings("serial") +public class UInt16 extends Number implements Comparable { + /** + * Maximum possible value. + */ + public static final int MAX_VALUE = 65535; + /** + * Minimum possible value. + */ + public static final int MIN_VALUE = 0; + private int value; + + /** + * Create a UInt16 from an int. + * + * @param value Must be within MIN_VALUE–MAX_VALUE + * @throws NumberFormatException if value is not between MIN_VALUE and MAX_VALUE + */ + public UInt16(int value) { + if (value < MIN_VALUE || value > MAX_VALUE) + throw new NumberFormatException(MessageFormat.format(getString("isNotBetween"), new Object[]{value, MIN_VALUE, MAX_VALUE})); + this.value = value; + } + + /** + * Create a UInt16 from a String. + * + * @param value Must parse to a valid integer within MIN_VALUE–MAX_VALUE + * @throws NumberFormatException if value is not an integer between MIN_VALUE and MAX_VALUE + */ + public UInt16(String value) { + this(Integer.parseInt(value)); + } + + /** + * The value of this as a byte. + */ + public byte byteValue() { + return (byte) value; + } + + /** + * The value of this as a double. + */ + public double doubleValue() { + return (double) value; + } + + /** + * The value of this as a float. + */ + public float floatValue() { + return (float) value; + } + + /** + * The value of this as a int. + */ + public int intValue() { + return /*(int)*/ value; + } + + /** + * The value of this as a long. + */ + public long longValue() { + return (long) value; + } + + /** + * The value of this as a short. + */ + public short shortValue() { + return (short) value; + } + + /** + * Test two UInt16s for equality. + */ + public boolean equals(Object o) { + return o instanceof UInt16 && ((UInt16) o).value == this.value; + } + + public int hashCode() { + return /*(int)*/ value; + } + + /** + * Compare two UInt16s. + * + * @return 0 if equal, -ve or +ve if they are different. + */ + public int compareTo(UInt16 other) { + return /*(int)*/ (this.value - other.value); + } + + /** + * The value of this as a string. + */ + public String toString() { + return "" + value; + } +} diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/UInt32.java b/federation/sssd/src/main/java/org/freedesktop/dbus/UInt32.java new file mode 100644 index 0000000000..465191934d --- /dev/null +++ b/federation/sssd/src/main/java/org/freedesktop/dbus/UInt32.java @@ -0,0 +1,122 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; + +import java.text.MessageFormat; + +import static org.freedesktop.dbus.Gettext.getString; + +/** + * Class to represent unsigned 32-bit numbers. + */ +@SuppressWarnings("serial") +public class UInt32 extends Number implements Comparable { + /** + * Maximum allowed value + */ + public static final long MAX_VALUE = 4294967295L; + /** + * Minimum allowed value + */ + public static final long MIN_VALUE = 0; + private long value; + + /** + * Create a UInt32 from a long. + * + * @param value Must be a valid integer within MIN_VALUE–MAX_VALUE + * @throws NumberFormatException if value is not between MIN_VALUE and MAX_VALUE + */ + public UInt32(long value) { + if (value < MIN_VALUE || value > MAX_VALUE) + throw new NumberFormatException(MessageFormat.format(getString("isNotBetween"), new Object[]{value, MIN_VALUE, MAX_VALUE})); + this.value = value; + } + + /** + * Create a UInt32 from a String. + * + * @param value Must parse to a valid integer within MIN_VALUE–MAX_VALUE + * @throws NumberFormatException if value is not an integer between MIN_VALUE and MAX_VALUE + */ + public UInt32(String value) { + this(Long.parseLong(value)); + } + + /** + * The value of this as a byte. + */ + public byte byteValue() { + return (byte) value; + } + + /** + * The value of this as a double. + */ + public double doubleValue() { + return (double) value; + } + + /** + * The value of this as a float. + */ + public float floatValue() { + return (float) value; + } + + /** + * The value of this as a int. + */ + public int intValue() { + return (int) value; + } + + /** + * The value of this as a long. + */ + public long longValue() { + return /*(long)*/ value; + } + + /** + * The value of this as a short. + */ + public short shortValue() { + return (short) value; + } + + /** + * Test two UInt32s for equality. + */ + public boolean equals(Object o) { + return o instanceof UInt32 && ((UInt32) o).value == this.value; + } + + public int hashCode() { + return (int) value; + } + + /** + * Compare two UInt32s. + * + * @return 0 if equal, -ve or +ve if they are different. + */ + public int compareTo(UInt32 other) { + return (int) (this.value - other.value); + } + + /** + * The value of this as a string + */ + public String toString() { + return "" + value; + } +} diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/UInt64.java b/federation/sssd/src/main/java/org/freedesktop/dbus/UInt64.java new file mode 100644 index 0000000000..fde2cf0260 --- /dev/null +++ b/federation/sssd/src/main/java/org/freedesktop/dbus/UInt64.java @@ -0,0 +1,203 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; + +import java.math.BigInteger; +import java.text.MessageFormat; + +import static org.freedesktop.dbus.Gettext.getString; + +/** + * Class to represent unsigned 64-bit numbers. + * Warning: Any functions which take or return a long + * are restricted to the range of a signed 64bit number. + * Use the BigInteger methods if you wish access to the full + * range. + */ +@SuppressWarnings("serial") +public class UInt64 extends Number implements Comparable { + /** + * Maximum allowed value (when accessed as a long) + */ + public static final long MAX_LONG_VALUE = Long.MAX_VALUE; + /** + * Maximum allowed value (when accessed as a BigInteger) + */ + public static final BigInteger MAX_BIG_VALUE = new BigInteger("18446744073709551615"); + /** + * Minimum allowed value + */ + public static final long MIN_VALUE = 0; + private BigInteger value; + private long top; + private long bottom; + + /** + * Create a UInt64 from a long. + * + * @param value Must be a valid integer within MIN_VALUE–MAX_VALUE + * @throws NumberFormatException if value is not between MIN_VALUE and MAX_VALUE + */ + public UInt64(long value) { + if (value < MIN_VALUE || value > MAX_LONG_VALUE) + throw new NumberFormatException(MessageFormat.format(getString("isNotBetween"), new Object[]{value, MIN_VALUE, MAX_LONG_VALUE})); + this.value = new BigInteger("" + value); + this.top = this.value.shiftRight(32).and(new BigInteger("4294967295")).longValue(); + this.bottom = this.value.and(new BigInteger("4294967295")).longValue(); + } + + /** + * Create a UInt64 from two longs. + * + * @param top Most significant 4 bytes. + * @param bottom Least significant 4 bytes. + */ + public UInt64(long top, long bottom) { + BigInteger a = new BigInteger("" + top); + a = a.shiftLeft(32); + a = a.add(new BigInteger("" + bottom)); + if (0 > a.compareTo(BigInteger.ZERO)) + throw new NumberFormatException(MessageFormat.format(getString("isNotBetween"), new Object[]{a, MIN_VALUE, MAX_BIG_VALUE})); + if (0 < a.compareTo(MAX_BIG_VALUE)) + throw new NumberFormatException(MessageFormat.format(getString("isNotBetween"), new Object[]{a, MIN_VALUE, MAX_BIG_VALUE})); + this.value = a; + this.top = top; + this.bottom = bottom; + } + + /** + * Create a UInt64 from a BigInteger + * + * @param value Must be a valid BigInteger between MIN_VALUE–MAX_BIG_VALUE + * @throws NumberFormatException if value is not an integer between MIN_VALUE and MAX_BIG_VALUE + */ + public UInt64(BigInteger value) { + if (null == value) + throw new NumberFormatException(MessageFormat.format(getString("isNotBetween"), new Object[]{value, MIN_VALUE, MAX_BIG_VALUE})); + if (0 > value.compareTo(BigInteger.ZERO)) + throw new NumberFormatException(MessageFormat.format(getString("isNotBetween"), new Object[]{value, MIN_VALUE, MAX_BIG_VALUE})); + if (0 < value.compareTo(MAX_BIG_VALUE)) + throw new NumberFormatException(MessageFormat.format(getString("isNotBetween"), new Object[]{value, MIN_VALUE, MAX_BIG_VALUE})); + this.value = value; + this.top = this.value.shiftRight(32).and(new BigInteger("4294967295")).longValue(); + this.bottom = this.value.and(new BigInteger("4294967295")).longValue(); + } + + /** + * Create a UInt64 from a String. + * + * @param value Must parse to a valid integer within MIN_VALUE–MAX_BIG_VALUE + * @throws NumberFormatException if value is not an integer between MIN_VALUE and MAX_BIG_VALUE + */ + public UInt64(String value) { + if (null == value) + throw new NumberFormatException(MessageFormat.format(getString("isNotBetween"), new Object[]{value, MIN_VALUE, MAX_BIG_VALUE})); + BigInteger a = new BigInteger(value); + if (0 > a.compareTo(BigInteger.ZERO)) + throw new NumberFormatException(MessageFormat.format(getString("isNotBetween"), new Object[]{value, MIN_VALUE, MAX_BIG_VALUE})); + if (0 < a.compareTo(MAX_BIG_VALUE)) + throw new NumberFormatException(MessageFormat.format(getString("isNotBetween"), new Object[]{value, MIN_VALUE, MAX_BIG_VALUE})); + this.value = a; + this.top = this.value.shiftRight(32).and(new BigInteger("4294967295")).longValue(); + this.bottom = this.value.and(new BigInteger("4294967295")).longValue(); + } + + /** + * The value of this as a BigInteger. + */ + public BigInteger value() { + return value; + } + + /** + * The value of this as a byte. + */ + public byte byteValue() { + return value.byteValue(); + } + + /** + * The value of this as a double. + */ + public double doubleValue() { + return value.doubleValue(); + } + + /** + * The value of this as a float. + */ + public float floatValue() { + return value.floatValue(); + } + + /** + * The value of this as a int. + */ + public int intValue() { + return value.intValue(); + } + + /** + * The value of this as a long. + */ + public long longValue() { + return value.longValue(); + } + + /** + * The value of this as a short. + */ + public short shortValue() { + return value.shortValue(); + } + + /** + * Test two UInt64s for equality. + */ + public boolean equals(Object o) { + return o instanceof UInt64 && this.value.equals(((UInt64) o).value); + } + + public int hashCode() { + return value.hashCode(); + } + + /** + * Compare two UInt32s. + * + * @return 0 if equal, -ve or +ve if they are different. + */ + public int compareTo(UInt64 other) { + return this.value.compareTo(other.value); + } + + /** + * The value of this as a string. + */ + public String toString() { + return value.toString(); + } + + /** + * Most significant 4 bytes. + */ + public long top() { + return top; + } + + /** + * Least significant 4 bytes. + */ + public long bottom() { + return bottom; + } +} + diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/Variant.java b/federation/sssd/src/main/java/org/freedesktop/dbus/Variant.java new file mode 100644 index 0000000000..8fe21a592e --- /dev/null +++ b/federation/sssd/src/main/java/org/freedesktop/dbus/Variant.java @@ -0,0 +1,136 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; + +import cx.ath.matthew.debug.Debug; +import org.freedesktop.dbus.exceptions.DBusException; + +import java.lang.reflect.Type; +import java.text.MessageFormat; +import java.util.Vector; + +import static org.freedesktop.dbus.Gettext.getString; + +/** + * A Wrapper class for Variant values. + * A method on DBus can send or receive a Variant. + * This will wrap another value whose type is determined at runtime. + * The Variant may be parameterized to restrict the types it may accept. + */ +public class Variant { + private final T o; + private final Type type; + private final String sig; + + /** + * Create a Variant from a basic type object. + * + * @param o The wrapped value. + * @throws IllegalArugmentException If you try and wrap Null or an object of a non-basic type. + */ + public Variant(T o) throws IllegalArgumentException { + if (null == o) throw new IllegalArgumentException(getString("cannotWrapNullInVariant")); + type = o.getClass(); + try { + String[] ss = Marshalling.getDBusType(o.getClass(), true); + if (ss.length != 1) + throw new IllegalArgumentException(getString("cannotWrapMultiValuedInVariant") + type); + this.sig = ss[0]; + } catch (DBusException DBe) { + if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, DBe); + throw new IllegalArgumentException(MessageFormat.format(getString("cannotWrapUnqualifiedVariant"), new Object[]{o.getClass(), DBe.getMessage()})); + } + this.o = o; + } + + /** + * Create a Variant. + * + * @param o The wrapped value. + * @param type The explicit type of the value. + * @throws IllegalArugmentException If you try and wrap Null or an object which cannot be sent over DBus. + */ + public Variant(T o, Type type) throws IllegalArgumentException { + if (null == o) throw new IllegalArgumentException(getString("cannotWrapNullInVariant")); + this.type = type; + try { + String[] ss = Marshalling.getDBusType(type); + if (ss.length != 1) + throw new IllegalArgumentException(getString("cannotWrapMultiValuedInVariant") + type); + this.sig = ss[0]; + } catch (DBusException DBe) { + if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, DBe); + throw new IllegalArgumentException(MessageFormat.format(getString("cannotWrapUnqualifiedVariant"), new Object[]{type, DBe.getMessage()})); + } + this.o = o; + } + + /** + * Create a Variant. + * + * @param o The wrapped value. + * @param sig The explicit type of the value, as a dbus type string. + * @throws IllegalArugmentException If you try and wrap Null or an object which cannot be sent over DBus. + */ + public Variant(T o, String sig) throws IllegalArgumentException { + if (null == o) throw new IllegalArgumentException(getString("cannotWrapNullInVariant")); + this.sig = sig; + try { + Vector ts = new Vector(); + Marshalling.getJavaType(sig, ts, 1); + if (ts.size() != 1) + throw new IllegalArgumentException(getString("cannotWrapNoTypesInVariant") + sig); + this.type = ts.get(0); + } catch (DBusException DBe) { + if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, DBe); + throw new IllegalArgumentException(MessageFormat.format(getString("cannotWrapUnqualifiedVariant"), new Object[]{sig, DBe.getMessage()})); + } + this.o = o; + } + + /** + * Return the wrapped value. + */ + public T getValue() { + return o; + } + + /** + * Return the type of the wrapped value. + */ + public Type getType() { + return type; + } + + /** + * Return the dbus signature of the wrapped value. + */ + public String getSig() { + return sig; + } + + /** + * Format the Variant as a string. + */ + public String toString() { + return "[" + o + "]"; + } + + /** + * Compare this Variant with another by comparing contents + */ + @SuppressWarnings("unchecked") + public boolean equals(Object other) { + if (null == other) return false; + if (!(other instanceof Variant)) return false; + return this.o.equals(((Variant) other).o); + } +} diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/exceptions/DBusException.java b/federation/sssd/src/main/java/org/freedesktop/dbus/exceptions/DBusException.java new file mode 100644 index 0000000000..d9c7e93e6a --- /dev/null +++ b/federation/sssd/src/main/java/org/freedesktop/dbus/exceptions/DBusException.java @@ -0,0 +1,24 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus.exceptions; + +/** + * An exception within DBus. + */ +@SuppressWarnings("serial") +public class DBusException extends Exception { + /** + * Create an exception with the specified message + */ + public DBusException(String message) { + super(message); + } +} diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/exceptions/DBusExecutionException.java b/federation/sssd/src/main/java/org/freedesktop/dbus/exceptions/DBusExecutionException.java new file mode 100644 index 0000000000..641a96729d --- /dev/null +++ b/federation/sssd/src/main/java/org/freedesktop/dbus/exceptions/DBusExecutionException.java @@ -0,0 +1,39 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus.exceptions; + +/** + * An exception while running a remote method within DBus. + */ +@SuppressWarnings("serial") +public class DBusExecutionException extends RuntimeException { + private String type; + + /** + * Create an exception with the specified message + */ + public DBusExecutionException(String message) { + super(message); + } + + public void setType(String type) { + this.type = type; + } + + /** + * Get the DBus type of this exception. Use if this + * was an exception we don't have a class file for. + */ + public String getType() { + if (null == type) return getClass().getName(); + else return type; + } +} diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/exceptions/FatalDBusException.java b/federation/sssd/src/main/java/org/freedesktop/dbus/exceptions/FatalDBusException.java new file mode 100644 index 0000000000..90002de5a3 --- /dev/null +++ b/federation/sssd/src/main/java/org/freedesktop/dbus/exceptions/FatalDBusException.java @@ -0,0 +1,18 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus.exceptions; + +@SuppressWarnings("serial") +public class FatalDBusException extends DBusException implements FatalException { + public FatalDBusException(String message) { + super(message); + } +} diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/exceptions/FatalException.java b/federation/sssd/src/main/java/org/freedesktop/dbus/exceptions/FatalException.java new file mode 100644 index 0000000000..58e3220428 --- /dev/null +++ b/federation/sssd/src/main/java/org/freedesktop/dbus/exceptions/FatalException.java @@ -0,0 +1,14 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus.exceptions; + +public interface FatalException { +} diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/exceptions/InternalMessageException.java b/federation/sssd/src/main/java/org/freedesktop/dbus/exceptions/InternalMessageException.java new file mode 100644 index 0000000000..ab9ecc1b13 --- /dev/null +++ b/federation/sssd/src/main/java/org/freedesktop/dbus/exceptions/InternalMessageException.java @@ -0,0 +1,18 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus.exceptions; + +@SuppressWarnings("serial") +public class InternalMessageException extends DBusExecutionException implements NonFatalException { + public InternalMessageException(String message) { + super(message); + } +} diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/exceptions/MarshallingException.java b/federation/sssd/src/main/java/org/freedesktop/dbus/exceptions/MarshallingException.java new file mode 100644 index 0000000000..3635456488 --- /dev/null +++ b/federation/sssd/src/main/java/org/freedesktop/dbus/exceptions/MarshallingException.java @@ -0,0 +1,18 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus.exceptions; + +@SuppressWarnings("serial") +public class MarshallingException extends DBusException implements NonFatalException { + public MarshallingException(String message) { + super(message); + } +} diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/exceptions/MessageFormatException.java b/federation/sssd/src/main/java/org/freedesktop/dbus/exceptions/MessageFormatException.java new file mode 100644 index 0000000000..e8a19389f7 --- /dev/null +++ b/federation/sssd/src/main/java/org/freedesktop/dbus/exceptions/MessageFormatException.java @@ -0,0 +1,21 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus.exceptions; + +/** + * Thrown if a message is formatted incorrectly. + */ +@SuppressWarnings("serial") +public class MessageFormatException extends DBusException implements NonFatalException { + public MessageFormatException(String message) { + super(message); + } +} diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/exceptions/MessageProtocolVersionException.java b/federation/sssd/src/main/java/org/freedesktop/dbus/exceptions/MessageProtocolVersionException.java new file mode 100644 index 0000000000..c093b41ccc --- /dev/null +++ b/federation/sssd/src/main/java/org/freedesktop/dbus/exceptions/MessageProtocolVersionException.java @@ -0,0 +1,20 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus.exceptions; + +import java.io.IOException; + +@SuppressWarnings("serial") +public class MessageProtocolVersionException extends IOException implements FatalException { + public MessageProtocolVersionException(String message) { + super(message); + } +} diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/exceptions/MessageTypeException.java b/federation/sssd/src/main/java/org/freedesktop/dbus/exceptions/MessageTypeException.java new file mode 100644 index 0000000000..21028e107e --- /dev/null +++ b/federation/sssd/src/main/java/org/freedesktop/dbus/exceptions/MessageTypeException.java @@ -0,0 +1,20 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus.exceptions; + +import java.io.IOException; + +@SuppressWarnings("serial") +public class MessageTypeException extends IOException implements NonFatalException { + public MessageTypeException(String message) { + super(message); + } +} diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/exceptions/NonFatalException.java b/federation/sssd/src/main/java/org/freedesktop/dbus/exceptions/NonFatalException.java new file mode 100644 index 0000000000..e6a0f36d3f --- /dev/null +++ b/federation/sssd/src/main/java/org/freedesktop/dbus/exceptions/NonFatalException.java @@ -0,0 +1,14 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus.exceptions; + +public interface NonFatalException { +} diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/exceptions/NotConnected.java b/federation/sssd/src/main/java/org/freedesktop/dbus/exceptions/NotConnected.java new file mode 100644 index 0000000000..0610c2065e --- /dev/null +++ b/federation/sssd/src/main/java/org/freedesktop/dbus/exceptions/NotConnected.java @@ -0,0 +1,21 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus.exceptions; + +/** + * Thrown if a DBus action is called when not connected to the Bus. + */ +@SuppressWarnings("serial") +public class NotConnected extends DBusExecutionException implements FatalException { + public NotConnected(String message) { + super(message); + } +} diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/exceptions/UnknownTypeCodeException.java b/federation/sssd/src/main/java/org/freedesktop/dbus/exceptions/UnknownTypeCodeException.java new file mode 100644 index 0000000000..af350758ed --- /dev/null +++ b/federation/sssd/src/main/java/org/freedesktop/dbus/exceptions/UnknownTypeCodeException.java @@ -0,0 +1,20 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus.exceptions; + +import static org.freedesktop.dbus.Gettext.getString; + +@SuppressWarnings("serial") +public class UnknownTypeCodeException extends DBusException implements NonFatalException { + public UnknownTypeCodeException(byte code) { + super(getString("invalidDBusCode") + code); + } +} diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/types/DBusListType.java b/federation/sssd/src/main/java/org/freedesktop/dbus/types/DBusListType.java new file mode 100644 index 0000000000..a00c3076d1 --- /dev/null +++ b/federation/sssd/src/main/java/org/freedesktop/dbus/types/DBusListType.java @@ -0,0 +1,44 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus.types; + +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.List; + +/** + * The type of a list. + * Should be used whenever you need a Type variable for a list. + */ +public class DBusListType implements ParameterizedType { + private Type v; + + /** + * Create a List type. + * + * @param v Type of the list contents. + */ + public DBusListType(Type v) { + this.v = v; + } + + public Type[] getActualTypeArguments() { + return new Type[]{v}; + } + + public Type getRawType() { + return List.class; + } + + public Type getOwnerType() { + return null; + } +} diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/types/DBusMapType.java b/federation/sssd/src/main/java/org/freedesktop/dbus/types/DBusMapType.java new file mode 100644 index 0000000000..754e0a2982 --- /dev/null +++ b/federation/sssd/src/main/java/org/freedesktop/dbus/types/DBusMapType.java @@ -0,0 +1,47 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus.types; + +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.Map; + +/** + * The type of a map. + * Should be used whenever you need a Type variable for a map. + */ +public class DBusMapType implements ParameterizedType { + private Type k; + private Type v; + + /** + * Create a map type. + * + * @param k The type of the keys. + * @param v The type of the values. + */ + public DBusMapType(Type k, Type v) { + this.k = k; + this.v = v; + } + + public Type[] getActualTypeArguments() { + return new Type[]{k, v}; + } + + public Type getRawType() { + return Map.class; + } + + public Type getOwnerType() { + return null; + } +} diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/types/DBusStructType.java b/federation/sssd/src/main/java/org/freedesktop/dbus/types/DBusStructType.java new file mode 100644 index 0000000000..fda2065601 --- /dev/null +++ b/federation/sssd/src/main/java/org/freedesktop/dbus/types/DBusStructType.java @@ -0,0 +1,45 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus.types; + +import org.freedesktop.dbus.Struct; + +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; + +/** + * The type of a struct. + * Should be used whenever you need a Type variable for a struct. + */ +public class DBusStructType implements ParameterizedType { + private Type[] contents; + + /** + * Create a struct type. + * + * @param contents The types contained in this struct. + */ + public DBusStructType(Type... contents) { + this.contents = contents; + } + + public Type[] getActualTypeArguments() { + return contents; + } + + public Type getRawType() { + return Struct.class; + } + + public Type getOwnerType() { + return null; + } +} diff --git a/federation/sssd/src/main/java/org/freedesktop/sssd/infopipe/Cache.java b/federation/sssd/src/main/java/org/freedesktop/sssd/infopipe/Cache.java new file mode 100644 index 0000000000..1a45839579 --- /dev/null +++ b/federation/sssd/src/main/java/org/freedesktop/sssd/infopipe/Cache.java @@ -0,0 +1,33 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.freedesktop.sssd.infopipe; + +import org.freedesktop.dbus.DBusInterface; + +import java.util.List; + +/** + * @author Bruno Oliveira. + */ +public interface Cache extends DBusInterface { + + List List(); + + List ListByDomain(String domain_name); + +} diff --git a/federation/sssd/src/main/java/org/freedesktop/sssd/infopipe/InfoPipe.java b/federation/sssd/src/main/java/org/freedesktop/sssd/infopipe/InfoPipe.java new file mode 100644 index 0000000000..6152d26a25 --- /dev/null +++ b/federation/sssd/src/main/java/org/freedesktop/sssd/infopipe/InfoPipe.java @@ -0,0 +1,44 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.freedesktop.sssd.infopipe; + +import org.freedesktop.dbus.DBusInterface; +import org.freedesktop.dbus.DBusInterfaceName; +import org.freedesktop.dbus.DBusMemberName; +import org.freedesktop.dbus.Variant; + +import java.util.List; +import java.util.Map; + +/** + * @author Bruno Oliveira. + */ +@DBusInterfaceName("org.freedesktop.sssd.infopipe") +public interface InfoPipe extends DBusInterface { + + String OBJECTPATH = "/org/freedesktop/sssd/infopipe"; + String BUSNAME = "org.freedesktop.sssd.infopipe"; + + + @DBusMemberName("GetUserAttr") + Map getUserAttributes(String user, List attr); + + @DBusMemberName("GetUserGroups") + List getUserGroups(String user); + +} \ No newline at end of file diff --git a/federation/sssd/src/main/java/org/freedesktop/sssd/infopipe/User.java b/federation/sssd/src/main/java/org/freedesktop/sssd/infopipe/User.java new file mode 100644 index 0000000000..896b80a0f6 --- /dev/null +++ b/federation/sssd/src/main/java/org/freedesktop/sssd/infopipe/User.java @@ -0,0 +1,35 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.freedesktop.sssd.infopipe; + +import org.freedesktop.dbus.DBusInterface; +import org.freedesktop.dbus.DBusInterfaceName; +import org.freedesktop.dbus.DBusMemberName; + +/** + * @author Bruno Oliveira. + */ +@DBusInterfaceName("org.freedesktop.sssd.infopipe.Users") +public interface User extends DBusInterface { + + String OBJECTPATH = "/org/freedesktop/sssd/infopipe/Users"; + + @DBusMemberName("FindByCertificate") + DBusInterface findByCertificate(String pem_cert); + +} diff --git a/federation/sssd/src/main/java/org/jvnet/libpam/PAM.java b/federation/sssd/src/main/java/org/jvnet/libpam/PAM.java new file mode 100644 index 0000000000..7de5f902c1 --- /dev/null +++ b/federation/sssd/src/main/java/org/jvnet/libpam/PAM.java @@ -0,0 +1,188 @@ +/* + * The MIT License + * + * Copyright (c) 2009, Sun Microsystems, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.jvnet.libpam; + +import com.sun.jna.Pointer; +import com.sun.jna.ptr.PointerByReference; +import org.jboss.logging.Logger; +import org.jvnet.libpam.impl.CLibrary.passwd; +import org.jvnet.libpam.impl.PAMLibrary.pam_conv; +import org.jvnet.libpam.impl.PAMLibrary.pam_conv.PamCallback; +import org.jvnet.libpam.impl.PAMLibrary.pam_handle_t; +import org.jvnet.libpam.impl.PAMLibrary.pam_message; +import org.jvnet.libpam.impl.PAMLibrary.pam_response; + +import java.util.Set; + +import static com.sun.jna.Native.POINTER_SIZE; +import static org.jvnet.libpam.impl.CLibrary.libc; +import static org.jvnet.libpam.impl.PAMLibrary.PAM_CONV_ERR; +import static org.jvnet.libpam.impl.PAMLibrary.PAM_PROMPT_ECHO_OFF; +import static org.jvnet.libpam.impl.PAMLibrary.PAM_SUCCESS; +import static org.jvnet.libpam.impl.PAMLibrary.PAM_USER; +import static org.jvnet.libpam.impl.PAMLibrary.libpam; + +/** + * PAM authenticator. + *

+ *

+ * Instances are thread unsafe and non reentrant. An instace cannot be reused + * to authenticate multiple users. + *

+ *

+ * For an overview of PAM programming, refer to the following resources: + *

+ *

+ * + * @author Kohsuke Kawaguchi + */ +public class PAM { + private pam_handle_t pht; + private int ret; + + /** + * Temporarily stored to pass a value from {@link #authenticate(String, String...)} + * to {@link pam_conv}. + */ + private String[] factors; + + /** + * Creates a new authenticator. + * + * @param serviceName PAM service name. This corresponds to the service name that shows up + * in the PAM configuration, + */ + public PAM(String serviceName) throws PAMException { + pam_conv conv = new pam_conv(new PamCallback() { + public int callback(int num_msg, Pointer msg, Pointer resp, Pointer _) { + LOGGER.debug("pam_conv num_msg=" + num_msg); + if (factors == null) + return PAM_CONV_ERR; + + // allocates pam_response[num_msg]. the caller will free this + Pointer m = libc.calloc(pam_response.SIZE, num_msg); + resp.setPointer(0, m); + + for (int i = 0; i < factors.length; i++) { + pam_message pm = new pam_message(msg.getPointer(POINTER_SIZE * i)); + LOGGER.debug(pm.msg_style + ":" + pm.msg); + if (pm.msg_style == PAM_PROMPT_ECHO_OFF) { + pam_response r = new pam_response(m.share(pam_response.SIZE * i)); + r.setResp(factors[i]); + r.write(); // write to (*resp)[i] + } + } + + return PAM_SUCCESS; + } + }); + + PointerByReference phtr = new PointerByReference(); + check(libpam.pam_start(serviceName, null, conv, phtr), "pam_start failed"); + pht = new pam_handle_t(phtr.getValue()); + } + + private void check(int ret, String msg) throws PAMException { + this.ret = ret; + if (ret != 0) { + if (pht != null) + throw new PAMException(msg + " : " + libpam.pam_strerror(pht, ret)); + else + throw new PAMException(msg); + } + } + + /** + * Authenticate the user with a password. + * + * @return Upon a successful authentication, return information about the user. + * @throws PAMException If the authentication fails. + */ + public UnixUser authenticate(String username, String... factors) throws PAMException { + this.factors = factors; + try { + check(libpam.pam_set_item(pht, PAM_USER, username), "pam_set_item failed"); + check(libpam.pam_authenticate(pht, 0), "pam_authenticate failed"); + check(libpam.pam_setcred(pht, 0), "pam_setcred failed"); + // several different error code seem to be used to represent authentication failures +// check(libpam.pam_acct_mgmt(pht,0),"pam_acct_mgmt failed"); + + PointerByReference r = new PointerByReference(); + check(libpam.pam_get_item(pht, PAM_USER, r), "pam_get_item failed"); + String userName = r.getValue().getString(0); + passwd pwd = libc.getpwnam(userName); + if (pwd == null) + throw new PAMException("Authentication succeeded but no user information is available"); + return new UnixUser(userName, pwd); + } finally { + this.factors = null; + } + } + + /** + * Returns the groups a user belongs to + * + * @param username + * @return Set of group names + * @throws PAMException + * @deprecated Pointless and ugly convenience method. + */ + public Set getGroupsOfUser(String username) throws PAMException { + return new UnixUser(username).getGroups(); + } + + /** + * After a successful authentication, call this method to obtain the effective user name. + * This can be different from the user name that you passed to the {@link #authenticate(String, String)} + * method. + */ + + /** + * Performs an early disposal of the object, instead of letting this GC-ed. + * Since PAM may hold on to native resources that don't put pressure on Java GC, + * doing this is a good idea. + *

+ *

+ * This method is called by {@link #finalize()}, too, so it's not required + * to call this method explicitly, however. + */ + public void dispose() { + if (pht != null) { + libpam.pam_end(pht, ret); + pht = null; + } + } + + + @Override + protected void finalize() throws Throwable { + super.finalize(); + dispose(); + } + + private static final Logger LOGGER = Logger.getLogger(PAM.class.getName()); +} diff --git a/federation/sssd/src/main/java/org/jvnet/libpam/PAMException.java b/federation/sssd/src/main/java/org/jvnet/libpam/PAMException.java new file mode 100644 index 0000000000..d26faf74eb --- /dev/null +++ b/federation/sssd/src/main/java/org/jvnet/libpam/PAMException.java @@ -0,0 +1,48 @@ +/* + * The MIT License + * + * Copyright (c) 2009, Sun Microsystems, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.jvnet.libpam; + +/** + * Exception in PAM invoactions. + * + * @author Kohsuke Kawaguchi + */ +public class PAMException extends Exception { + public PAMException() { + } + + public PAMException(String message) { + super(message); + } + + public PAMException(String message, Throwable cause) { + super(message, cause); + } + + public PAMException(Throwable cause) { + super(cause); + } + + private static final long serialVersionUID = 1L; +} diff --git a/federation/sssd/src/main/java/org/jvnet/libpam/UnixUser.java b/federation/sssd/src/main/java/org/jvnet/libpam/UnixUser.java new file mode 100644 index 0000000000..bc1ceab8e1 --- /dev/null +++ b/federation/sssd/src/main/java/org/jvnet/libpam/UnixUser.java @@ -0,0 +1,159 @@ +/* + * The MIT License + * + * Copyright (c) 2009, Sun Microsystems, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.jvnet.libpam; + +import com.sun.jna.Memory; +import com.sun.jna.ptr.IntByReference; +import org.jvnet.libpam.impl.CLibrary.group; +import org.jvnet.libpam.impl.CLibrary.passwd; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import static org.jvnet.libpam.impl.CLibrary.libc; + +/** + * Represents an Unix user. Immutable. + * + * @author Kohsuke Kawaguchi + */ +public class UnixUser { + private final String userName, gecos, dir, shell; + private final int uid, gid; + private final Set groups; + + /*package*/ UnixUser(String userName, passwd pwd) throws PAMException { + this.userName = userName; + this.gecos = pwd.getPwGecos(); + this.dir = pwd.getPwDir(); + this.shell = pwd.getPwShell(); + this.uid = pwd.getPwUid(); + this.gid = pwd.getPwGid(); + + int sz = 4; /*sizeof(gid_t)*/ + + int ngroups = 64; + Memory m = new Memory(ngroups * sz); + IntByReference pngroups = new IntByReference(ngroups); + try { + if (libc.getgrouplist(userName, pwd.getPwGid(), m, pngroups) < 0) { + // allocate a bigger memory + m = new Memory(pngroups.getValue() * sz); + if (libc.getgrouplist(userName, pwd.getPwGid(), m, pngroups) < 0) + // shouldn't happen, but just in case. + throw new PAMException("getgrouplist failed"); + } + ngroups = pngroups.getValue(); + } catch (LinkageError e) { + // some platform, notably Solaris, doesn't have the getgrouplist function + ngroups = libc._getgroupsbymember(userName, m, ngroups, 0); + if (ngroups < 0) + throw new PAMException("_getgroupsbymember failed"); + } + + groups = new HashSet(); + for (int i = 0; i < ngroups; i++) { + int gid = m.getInt(i * sz); + group grp = libc.getgrgid(gid); + if (grp == null) { + continue; + } + groups.add(grp.gr_name); + } + } + + public UnixUser(String userName) throws PAMException { + this(userName, passwd.loadPasswd(userName)); + } + + /** + * Copy constructor for mocking. Not intended for regular use. Only for testing. + * This signature may change in the future. + */ + protected UnixUser(String userName, String gecos, String dir, String shell, int uid, int gid, Set groups) { + this.userName = userName; + this.gecos = gecos; + this.dir = dir; + this.shell = shell; + this.uid = uid; + this.gid = gid; + this.groups = groups; + } + + /** + * Gets the unix account name. Never null. + */ + public String getUserName() { + return userName; + } + + /** + * Gets the UID of this user. + */ + public int getUID() { + return uid; + } + + /** + * Gets the GID of this user. + */ + public int getGID() { + return gid; + } + + /** + * Gets the gecos (the real name) of this user. + */ + public String getGecos() { + return gecos; + } + + /** + * Gets the home directory of this user. + */ + public String getDir() { + return dir; + } + + /** + * Gets the shell of this user. + */ + public String getShell() { + return shell; + } + + /** + * Gets the groups that this user belongs to. + * + * @return never null. + */ + public Set getGroups() { + return Collections.unmodifiableSet(groups); + } + + public static boolean exists(String name) { + return libc.getpwnam(name) != null; + } +} diff --git a/federation/sssd/src/main/java/org/jvnet/libpam/impl/BSDCLibrary.java b/federation/sssd/src/main/java/org/jvnet/libpam/impl/BSDCLibrary.java new file mode 100644 index 0000000000..0de8a2a7e0 --- /dev/null +++ b/federation/sssd/src/main/java/org/jvnet/libpam/impl/BSDCLibrary.java @@ -0,0 +1,34 @@ +/* + * The MIT License + * + * Copyright 2011, Sun Microsystems, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package org.jvnet.libpam.impl; + +/** + * @author Sebastian Sdorra + */ +public interface BSDCLibrary extends CLibrary { + + BSDPasswd getpwnam(String username); + +} diff --git a/federation/sssd/src/main/java/org/jvnet/libpam/impl/BSDPasswd.java b/federation/sssd/src/main/java/org/jvnet/libpam/impl/BSDPasswd.java new file mode 100644 index 0000000000..2e437bb660 --- /dev/null +++ b/federation/sssd/src/main/java/org/jvnet/libpam/impl/BSDPasswd.java @@ -0,0 +1,93 @@ +/* + * The MIT License + * + * Copyright 2011, Sun Microsystems, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package org.jvnet.libpam.impl; + +import org.jvnet.libpam.impl.CLibrary.passwd; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * FreeeBSD, OpenBSD and MacOS passwd + *

+ * struct passwd { + * char *pw_name; + * char *pw_passwd; + * uid_t pw_uid; + * gid_t pw_gid; + * time_t pw_change; + * char *pw_class; + * char *pw_gecos; + * char *pw_dir; + * char *pw_shell; + * time_t pw_expire; + * }; + * + * @author Sebastian Sdorra + */ +public class BSDPasswd extends passwd { + /* password change time */ + public long pw_change; + + /* user access class */ + public String pw_class; + + /* Honeywell login info */ + public String pw_gecos; + + /* home directory */ + public String pw_dir; + + /* default shell */ + public String pw_shell; + + /* account expiration */ + public long pw_expire; + + @Override + public String getPwGecos() { + return pw_gecos; + } + + @Override + public String getPwDir() { + return pw_dir; + } + + @Override + public String getPwShell() { + return pw_shell; + } + + @Override + protected List getFieldOrder() { + List fieldOrder = new ArrayList(super.getFieldOrder()); + fieldOrder.addAll(Arrays.asList("pw_change", "pw_class", "pw_gecos", + "pw_dir", "pw_shell", "pw_expire")); + return fieldOrder; + } + +} diff --git a/federation/sssd/src/main/java/org/jvnet/libpam/impl/CLibrary.java b/federation/sssd/src/main/java/org/jvnet/libpam/impl/CLibrary.java new file mode 100644 index 0000000000..e3b610536a --- /dev/null +++ b/federation/sssd/src/main/java/org/jvnet/libpam/impl/CLibrary.java @@ -0,0 +1,154 @@ +/* + * The MIT License + * + * Copyright (c) 2009, Sun Microsystems, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.jvnet.libpam.impl; + +import com.sun.jna.Library; +import com.sun.jna.Memory; +import com.sun.jna.Native; +import com.sun.jna.Platform; +import com.sun.jna.Pointer; +import com.sun.jna.Structure; +import com.sun.jna.ptr.IntByReference; +import org.jvnet.libpam.PAMException; + +import java.util.Arrays; +import java.util.List; + +/** + * @author Kohsuke Kawaguchi + */ +public interface CLibrary extends Library { + /** + * Comparing http://linux.die.net/man/3/getpwnam + * and my Mac OS X reveals that the structure of this field isn't very portable. + * In particular, we cannot read the real name reliably. + */ + public class passwd extends Structure { + /** + * User name. + */ + public String pw_name; + /** + * Encrypted password. + */ + public String pw_passwd; + public int pw_uid; + public int pw_gid; + + // ... there are a lot more fields + + public static passwd loadPasswd(String userName) throws PAMException { + passwd pwd = libc.getpwnam(userName); + if (pwd == null) { + throw new PAMException("No user information is available"); + } + return pwd; + } + + public String getPwName() { + return pw_name; + } + + public String getPwPasswd() { + return pw_passwd; + } + + public int getPwUid() { + return pw_uid; + } + + public int getPwGid() { + return pw_gid; + } + + public String getPwGecos() { + return null; + } + + public String getPwDir() { + return null; + } + + public String getPwShell() { + return null; + } + + protected List getFieldOrder() { + return Arrays.asList("pw_name", "pw_passwd", "pw_uid", "pw_gid"); + } + } + + public class group extends Structure { + public String gr_name; + // ... the rest of the field is not interesting for us + + protected List getFieldOrder() { + return Arrays.asList("gr_name"); + } + } + + Pointer calloc(int count, int size); + + Pointer strdup(String s); + + passwd getpwnam(String username); + + /** + * Lists up group IDs of the given user. On Linux and most BSDs, but not on Solaris. + * See http://www.gnu.org/software/hello/manual/gnulib/getgrouplist.html + */ + int getgrouplist(String user, int/*gid_t*/ group, Memory groups, IntByReference ngroups); + + /** + * getgrouplist equivalent on Solaris. + * See http://mail.opensolaris.org/pipermail/sparks-discuss/2008-September/000528.html + */ + int _getgroupsbymember(String user, Memory groups, int maxgids, int numgids); + + group getgrgid(int/*gid_t*/ gid); + + group getgrnam(String name); + + // other user/group related functions that are likely useful + // see http://www.gnu.org/software/libc/manual/html_node/Users-and-Groups.html#Users-and-Groups + + + public static final CLibrary libc = Instance.init(); + + static class Instance { + private static CLibrary init() { + if (Platform.isMac() || Platform.isOpenBSD()) { + return (CLibrary) Native.loadLibrary("c", BSDCLibrary.class); + } else if (Platform.isFreeBSD()) { + return (CLibrary) Native.loadLibrary("c", FreeBSDCLibrary.class); + } else if (Platform.isSolaris()) { + return (CLibrary) Native.loadLibrary("c", SolarisCLibrary.class); + } else if (Platform.isLinux()) { + return (CLibrary) Native.loadLibrary("c", LinuxCLibrary.class); + } else { + return (CLibrary) Native.loadLibrary("c", CLibrary.class); + } + } + } +} diff --git a/federation/sssd/src/main/java/org/jvnet/libpam/impl/FreeBSDCLibrary.java b/federation/sssd/src/main/java/org/jvnet/libpam/impl/FreeBSDCLibrary.java new file mode 100644 index 0000000000..638aec2e5c --- /dev/null +++ b/federation/sssd/src/main/java/org/jvnet/libpam/impl/FreeBSDCLibrary.java @@ -0,0 +1,34 @@ +/* + * The MIT License + * + * Copyright 2014, R. Tyler Croy, Sun Microsystems, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package org.jvnet.libpam.impl; + +/** + * @author R. Tyler Croy + */ +public interface FreeBSDCLibrary extends CLibrary { + + FreeBSDPasswd getpwnam(String username); + +} diff --git a/federation/sssd/src/main/java/org/jvnet/libpam/impl/FreeBSDPasswd.java b/federation/sssd/src/main/java/org/jvnet/libpam/impl/FreeBSDPasswd.java new file mode 100644 index 0000000000..28e519b561 --- /dev/null +++ b/federation/sssd/src/main/java/org/jvnet/libpam/impl/FreeBSDPasswd.java @@ -0,0 +1,98 @@ +/* + * The MIT License + * + * Copyright 2014, R. Tyler Croy, Sun Microsystems, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package org.jvnet.libpam.impl; + +import org.jvnet.libpam.impl.CLibrary.passwd; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * FreeeBSD + *

+ * struct passwd { + * char *pw_name; + * char *pw_passwd; + * uid_t pw_uid; + * gid_t pw_gid; + * time_t pw_change; + * char *pw_class; + * char *pw_gecos; + * char *pw_dir; + * char *pw_shell; + * time_t pw_expire; + * int pw_fields; + * }; + * + * @author R. Tyler Croy + */ + +public class FreeBSDPasswd extends passwd { + /* password change time */ + public long pw_change; + + /* user access class */ + public String pw_class; + + /* Honeywell login info */ + public String pw_gecos; + + /* home directory */ + public String pw_dir; + + /* default shell */ + public String pw_shell; + + /* account expiration */ + public long pw_expire; + + /* internal on FreeBSD? */ + public int pw_fields; + + @Override + public String getPwGecos() { + return pw_gecos; + } + + @Override + public String getPwDir() { + return pw_dir; + } + + @Override + public String getPwShell() { + return pw_shell; + } + + @Override + protected List getFieldOrder() { + List fieldOrder = new ArrayList(super.getFieldOrder()); + fieldOrder.addAll(Arrays.asList("pw_change", "pw_class", "pw_gecos", + "pw_dir", "pw_shell", "pw_expire", "pw_fields")); + return fieldOrder; + } + +} diff --git a/federation/sssd/src/main/java/org/jvnet/libpam/impl/LinuxCLibrary.java b/federation/sssd/src/main/java/org/jvnet/libpam/impl/LinuxCLibrary.java new file mode 100644 index 0000000000..2525bbe64a --- /dev/null +++ b/federation/sssd/src/main/java/org/jvnet/libpam/impl/LinuxCLibrary.java @@ -0,0 +1,34 @@ +/* + * The MIT License + * + * Copyright 2011, Sun Microsystems, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package org.jvnet.libpam.impl; + +/** + * @author Sebastian Sdorra + */ +public interface LinuxCLibrary extends CLibrary { + + LinuxPasswd getpwnam(String username); + +} diff --git a/federation/sssd/src/main/java/org/jvnet/libpam/impl/LinuxPasswd.java b/federation/sssd/src/main/java/org/jvnet/libpam/impl/LinuxPasswd.java new file mode 100644 index 0000000000..c1b167877c --- /dev/null +++ b/federation/sssd/src/main/java/org/jvnet/libpam/impl/LinuxPasswd.java @@ -0,0 +1,81 @@ +/* + * The MIT License + * + * Copyright 2011, Sun Microsystems, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package org.jvnet.libpam.impl; + +import org.jvnet.libpam.impl.CLibrary.passwd; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Linux passwd + *

+ * ?struct passwd + * { + * char *pw_name; + * char *pw_passwd; + * __uid_t pw_uid; + * __gid_t pw_gid; + * char *pw_gecos; + * char *pw_dir; + * char *pw_shell; + * }; + * + * @author Sebastian Sdorra + */ +public class LinuxPasswd extends passwd { + /* Honeywell login info */ + public String pw_gecos; + + /* home directory */ + public String pw_dir; + + /* default shell */ + public String pw_shell; + + + public String getPwGecos() { + return pw_gecos; + } + + @Override + public String getPwDir() { + return pw_dir; + } + + @Override + public String getPwShell() { + return pw_shell; + } + + @Override + protected List getFieldOrder() { + List fieldOrder = new ArrayList(super.getFieldOrder()); + fieldOrder.addAll(Arrays.asList("pw_gecos", "pw_dir", "pw_shell")); + return fieldOrder; + } + +} diff --git a/federation/sssd/src/main/java/org/jvnet/libpam/impl/PAMLibrary.java b/federation/sssd/src/main/java/org/jvnet/libpam/impl/PAMLibrary.java new file mode 100644 index 0000000000..a34ec3184a --- /dev/null +++ b/federation/sssd/src/main/java/org/jvnet/libpam/impl/PAMLibrary.java @@ -0,0 +1,163 @@ +/* + * The MIT License + * + * Copyright (c) 2009, Sun Microsystems, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.jvnet.libpam.impl; + +import com.sun.jna.Callback; +import com.sun.jna.Library; +import com.sun.jna.Native; +import com.sun.jna.Pointer; +import com.sun.jna.PointerType; +import com.sun.jna.Structure; +import com.sun.jna.ptr.PointerByReference; + +import java.util.Arrays; +import java.util.List; + +import static org.jvnet.libpam.impl.CLibrary.libc; + +/** + * libpam.so binding. + *

+ * See http://www.opengroup.org/onlinepubs/008329799/apdxa.htm + * for the online reference of pam_appl.h + * + * @author Kohsuke Kawaguchi + */ +public interface PAMLibrary extends Library { + class pam_handle_t extends PointerType { + public pam_handle_t() { + } + + public pam_handle_t(Pointer pointer) { + super(pointer); + } + } + + class pam_message extends Structure { + public int msg_style; + public String msg; + + /** + * Attach to the memory region pointed by the given pointer. + */ + public pam_message(Pointer src) { + useMemory(src); + read(); + } + + protected List getFieldOrder() { + return Arrays.asList("msg_style", "msg"); + } + } + + class pam_response extends Structure { + /** + * This is really a string, but this field needs to be malloc-ed by the conversation + * method, and to be freed by the caler, so I bind it to {@link Pointer} here. + *

+ * The man page doesn't say that, but see + * http://www.netbsd.org/docs/guide/en/chap-pam.html#pam-sample-conv + * This behavior is confirmed with a test, too; if I don't do strdup, + * libpam crashes. + */ + public Pointer resp; + public int resp_retcode; + + /** + * Attach to the memory region pointed by the given memory. + */ + public pam_response(Pointer src) { + useMemory(src); + read(); + } + + public pam_response() { + } + + /** + * Sets the response code. + */ + public void setResp(String msg) { + this.resp = libc.strdup(msg); + } + + protected List getFieldOrder() { + return Arrays.asList("resp", "resp_retcode"); + } + + public static final int SIZE = new pam_response().size(); + } + + class pam_conv extends Structure { + public interface PamCallback extends Callback { + /** + * According to http://www.netbsd.org/docs/guide/en/chap-pam.html#pam-sample-conv, + * resp and its member string both needs to be allocated by malloc, + * to be freed by the caller. + */ + int callback(int num_msg, Pointer msg, Pointer resp, Pointer _); + } + + public PamCallback conv; + public Pointer _; + + public pam_conv(PamCallback conv) { + this.conv = conv; + } + + protected List getFieldOrder() { + return Arrays.asList("conv", "_"); + } + } + + int pam_start(String service, String user, pam_conv conv, PointerByReference/* pam_handle_t** */ pamh_p); + + int pam_end(pam_handle_t handle, int pam_status); + + int pam_set_item(pam_handle_t handle, int item_type, String item); + + int pam_get_item(pam_handle_t handle, int item_type, PointerByReference item); + + int pam_authenticate(pam_handle_t handle, int flags); + + int pam_setcred(pam_handle_t handle, int flags); + + int pam_acct_mgmt(pam_handle_t handle, int flags); + + String pam_strerror(pam_handle_t handle, int pam_error); + + final int PAM_USER = 2; + + // error code + final int PAM_SUCCESS = 0; + final int PAM_CONV_ERR = 6; + + + final int PAM_PROMPT_ECHO_OFF = 1; /* Echo off when getting response */ + final int PAM_PROMPT_ECHO_ON = 2; /* Echo on when getting response */ + final int PAM_ERROR_MSG = 3; /* Error message */ + final int PAM_TEXT_INFO = 4; /* Textual information */ + + public static final PAMLibrary libpam = (PAMLibrary) Native.loadLibrary("pam", PAMLibrary.class); +} diff --git a/federation/sssd/src/main/java/org/jvnet/libpam/impl/SolarisCLibrary.java b/federation/sssd/src/main/java/org/jvnet/libpam/impl/SolarisCLibrary.java new file mode 100644 index 0000000000..6b538a2407 --- /dev/null +++ b/federation/sssd/src/main/java/org/jvnet/libpam/impl/SolarisCLibrary.java @@ -0,0 +1,35 @@ +/* + * The MIT License + * + * Copyright (c) 2009, Sun Microsystems, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + + +package org.jvnet.libpam.impl; + +/** + * @author Sebastian Sdorra + */ +public interface SolarisCLibrary extends CLibrary { + + SolarisPasswd getpwnam(String username); + +} diff --git a/federation/sssd/src/main/java/org/jvnet/libpam/impl/SolarisPasswd.java b/federation/sssd/src/main/java/org/jvnet/libpam/impl/SolarisPasswd.java new file mode 100644 index 0000000000..37ee749230 --- /dev/null +++ b/federation/sssd/src/main/java/org/jvnet/libpam/impl/SolarisPasswd.java @@ -0,0 +1,86 @@ +/* + * The MIT License + * + * Copyright (c) 2009, Sun Microsystems, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + + +package org.jvnet.libpam.impl; + +import org.jvnet.libpam.impl.CLibrary.passwd; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Solaris passwd + *

+ * struct passwd { + * char *pw_name; + * char *pw_passwd; + * uid_t pw_uid; + * gid_t pw_gid; + * char *pw_age; + * char *pw_comment; + * char *pw_gecos; + * char *pw_dir; + * char *pw_shell; + * }; + * + * @author Sebastian Sdorra + */ +public class SolarisPasswd extends passwd { + public String pw_age; + + public String pw_comment; + + public String pw_gecos; + + public String pw_dir; + + public String pw_shell; + + + @Override + public String getPwGecos() { + return pw_gecos; + } + + @Override + public String getPwDir() { + return pw_dir; + } + + @Override + public String getPwShell() { + return pw_shell; + } + + @Override + protected List getFieldOrder() { + List fieldOrder = new ArrayList(super.getFieldOrder()); + fieldOrder.addAll(Arrays.asList("pw_age", "pw_comment", "pw_gecos", + "pw_dir", "pw_shell")); + return fieldOrder; + } + +} diff --git a/federation/sssd/src/main/java/org/keycloak/federation/sssd/ReadonlySSSDUserModelDelegate.java b/federation/sssd/src/main/java/org/keycloak/federation/sssd/ReadonlySSSDUserModelDelegate.java new file mode 100755 index 0000000000..52061c9d18 --- /dev/null +++ b/federation/sssd/src/main/java/org/keycloak/federation/sssd/ReadonlySSSDUserModelDelegate.java @@ -0,0 +1,77 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.keycloak.federation.sssd; + +import org.keycloak.models.ModelReadOnlyException; +import org.keycloak.models.UserCredentialModel; +import org.keycloak.models.UserCredentialValueModel; +import org.keycloak.models.UserModel; +import org.keycloak.models.utils.UserModelDelegate; + +/** + * Readonly proxy for a SSSD UserModel that prevents attributes from being updated. + * + * @author Bill Burke + * @author Bruno Oliveira + * @version $Revision: 1 $ + */ +public class ReadonlySSSDUserModelDelegate extends UserModelDelegate implements UserModel { + + private final SSSDFederationProvider provider; + + public ReadonlySSSDUserModelDelegate(UserModel delegate, SSSDFederationProvider provider) { + super(delegate); + this.provider = provider; + } + + @Override + public void setUsername(String username) { + throw new ModelReadOnlyException("Federated storage is not writable"); + } + + @Override + public void setLastName(String lastName) { + throw new ModelReadOnlyException("Federated storage is not writable"); + } + + @Override + public void setFirstName(String first) { + throw new ModelReadOnlyException("Federated storage is not writable"); + } + + @Override + public void updateCredentialDirectly(UserCredentialValueModel cred) { + if (cred.getType().equals(UserCredentialModel.PASSWORD)) { + throw new IllegalStateException("Federated storage is not writable"); + } + super.updateCredentialDirectly(cred); + } + + @Override + public void updateCredential(UserCredentialModel cred) { + if (provider.getSupportedCredentialTypes(delegate).contains(cred.getType())) { + throw new ModelReadOnlyException("Federated storage is not writable"); + } + delegate.updateCredential(cred); + } + + @Override + public void setEmail(String email) { + throw new ModelReadOnlyException("Federated storage is not writable"); + } +} diff --git a/federation/sssd/src/main/java/org/keycloak/federation/sssd/SSSDFederationProvider.java b/federation/sssd/src/main/java/org/keycloak/federation/sssd/SSSDFederationProvider.java new file mode 100755 index 0000000000..a9089ec0b1 --- /dev/null +++ b/federation/sssd/src/main/java/org/keycloak/federation/sssd/SSSDFederationProvider.java @@ -0,0 +1,224 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.keycloak.federation.sssd; + +import org.freedesktop.dbus.Variant; +import org.jboss.logging.Logger; +import org.keycloak.federation.sssd.api.Sssd; +import org.keycloak.federation.sssd.impl.PAMAuthenticator; +import org.keycloak.models.CredentialValidationOutput; +import org.keycloak.models.GroupModel; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.RealmModel; +import org.keycloak.models.RoleModel; +import org.keycloak.models.UserCredentialModel; +import org.keycloak.models.UserFederationProvider; +import org.keycloak.models.UserFederationProviderModel; +import org.keycloak.models.UserModel; +import org.keycloak.models.utils.KeycloakModelUtils; +import org.keycloak.services.managers.UserManager; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * SPI provider implementation to retrieve data from SSSD and authenticate + * against PAM + * + * @author Bruno Oliveira + * @version $Revision: 1 $ + */ +public class SSSDFederationProvider implements UserFederationProvider { + + private static final Logger logger = Logger.getLogger(SSSDFederationProvider.class); + + protected static final Set supportedCredentialTypes = new HashSet<>(); + private final SSSDFederationProviderFactory factory; + protected KeycloakSession session; + protected UserFederationProviderModel model; + + public SSSDFederationProvider(KeycloakSession session, UserFederationProviderModel model, SSSDFederationProviderFactory sssdFederationProviderFactory) { + this.session = session; + this.model = model; + this.factory = sssdFederationProviderFactory; + } + + static { + supportedCredentialTypes.add(UserCredentialModel.PASSWORD); + } + + + @Override + public UserModel getUserByUsername(RealmModel realm, String username) { + return findOrCreateAuthenticatedUser(realm, username); + } + + /** + * Called after successful authentication + * + * @param realm realm + * @param username username without realm prefix + * @return user if found or successfully created. Null if user with same username already exists, but is not linked to this provider + */ + protected UserModel findOrCreateAuthenticatedUser(RealmModel realm, String username) { + UserModel user = session.userStorage().getUserByUsername(username, realm); + if (user != null) { + logger.debug("SSSD authenticated user " + username + " found in Keycloak storage"); + + if (!model.getId().equals(user.getFederationLink())) { + logger.warn("User with username " + username + " already exists, but is not linked to provider [" + model.getDisplayName() + "]"); + return null; + } else { + UserModel proxied = validateAndProxy(realm, user); + if (proxied != null) { + return proxied; + } else { + logger.warn("User with username " + username + " already exists and is linked to provider [" + model.getDisplayName() + + "] but principal is not correct."); + logger.warn("Will re-create user"); + new UserManager(session).removeUser(realm, user, session.userStorage()); + } + } + } + + logger.debug("SSSD authenticated user " + username + " not in Keycloak storage. Creating..."); + return importUserToKeycloak(realm, username); + } + + protected UserModel importUserToKeycloak(RealmModel realm, String username) { + Sssd sssd = new Sssd(username); + Map sssdUser = sssd.getUserAttributes(); + logger.debugf("Creating SSSD user: %s to local Keycloak storage", username); + UserModel user = session.userStorage().addUser(realm, username); + user.setEnabled(true); + user.setEmail(Sssd.getRawAttribute(sssdUser.get("mail"))); + user.setFirstName(Sssd.getRawAttribute(sssdUser.get("givenname"))); + user.setLastName(Sssd.getRawAttribute(sssdUser.get("sn"))); + for (String s : sssd.getUserGroups()) { + GroupModel group = KeycloakModelUtils.findGroupByPath(realm, "/" + s); + if (group == null) { + group = session.realms().createGroup(realm, s); + } + user.joinGroup(group); + } + user.setFederationLink(model.getId()); + return validateAndProxy(realm, user); + } + + @Override + public UserModel getUserByEmail(RealmModel realm, String email) { + return null; + } + + @Override + public List searchByAttributes(Map attributes, RealmModel realm, int maxResults) { + return Collections.emptyList(); + } + + @Override + public List getGroupMembers(RealmModel realm, GroupModel group, int firstResult, int maxResults) { + return Collections.emptyList(); + } + + @Override + public void preRemove(RealmModel realm) { + // complete We don't care about the realm being removed + } + + @Override + public void preRemove(RealmModel realm, RoleModel role) { + // complete we dont'care if a role is removed + + } + + @Override + public void preRemove(RealmModel realm, GroupModel group) { + // complete we dont'care if a role is removed + + } + + @Override + public boolean isValid(RealmModel realm, UserModel local) { + Map attributes = new Sssd(local.getUsername()).getUserAttributes(); + return Sssd.getRawAttribute(attributes.get("mail")).equalsIgnoreCase(local.getEmail()); + } + + @Override + public Set getSupportedCredentialTypes(UserModel user) { + return supportedCredentialTypes; + } + + @Override + public Set getSupportedCredentialTypes() { + return supportedCredentialTypes; + } + + @Override + public boolean validCredentials(RealmModel realm, UserModel user, List input) { + for (UserCredentialModel cred : input) { + if (cred.getType().equals(UserCredentialModel.PASSWORD)) { + PAMAuthenticator pam = factory.createPAMAuthenticator(user.getUsername(), cred.getValue()); + return (pam.authenticate() != null); + } + } + return false; + } + + @Override + public boolean validCredentials(RealmModel realm, UserModel user, UserCredentialModel... input) { + return validCredentials(realm, user, Arrays.asList(input)); + } + + @Override + public CredentialValidationOutput validCredentials(RealmModel realm, UserCredentialModel credential) { + return CredentialValidationOutput.failed(); + } + + @Override + public UserModel validateAndProxy(RealmModel realm, UserModel local) { + if (isValid(realm, local)) { + return new ReadonlySSSDUserModelDelegate(local, this); + } else { + return null; + } + } + + @Override + public boolean synchronizeRegistrations() { + return false; + } + + @Override + public UserModel register(RealmModel realm, UserModel user) { + throw new IllegalStateException("Registration not supported"); + } + + @Override + public boolean removeUser(RealmModel realm, UserModel user) { + return true; + } + + @Override + public void close() { + Sssd.disconnect(); + } +} diff --git a/federation/sssd/src/main/java/org/keycloak/federation/sssd/SSSDFederationProviderFactory.java b/federation/sssd/src/main/java/org/keycloak/federation/sssd/SSSDFederationProviderFactory.java new file mode 100755 index 0000000000..3140e9e48a --- /dev/null +++ b/federation/sssd/src/main/java/org/keycloak/federation/sssd/SSSDFederationProviderFactory.java @@ -0,0 +1,108 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.keycloak.federation.sssd; + +import org.jboss.logging.Logger; +import org.keycloak.Config; +import org.keycloak.federation.sssd.api.Sssd; +import org.keycloak.federation.sssd.impl.PAMAuthenticator; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.KeycloakSessionFactory; +import org.keycloak.models.UserFederationProvider; +import org.keycloak.models.UserFederationProviderFactory; +import org.keycloak.models.UserFederationProviderModel; +import org.keycloak.models.UserFederationSyncResult; +import org.keycloak.provider.EnvironmentDependentProviderFactory; + +import java.util.Date; +import java.util.HashSet; +import java.util.Set; + +/** + * @author Bruno Oliveira + * @version $Revision: 1 $ + */ +public class SSSDFederationProviderFactory implements UserFederationProviderFactory, EnvironmentDependentProviderFactory { + + private static final String PROVIDER_NAME = "sssd"; + private static final Logger logger = Logger.getLogger(SSSDFederationProvider.class); + + static final Set configOptions = new HashSet(); + + @Override + public String getId() { + return PROVIDER_NAME; + } + + @Override + public UserFederationProvider getInstance(KeycloakSession session, UserFederationProviderModel model) { + return new SSSDFederationProvider(session, model, this); + } + + /** + * List the configuration options to render and display in the admin console's generic management page for this + * plugin + * + * @return + */ + @Override + public Set getConfigurationOptions() { + return configOptions; + } + + @Override + public UserFederationProvider create(KeycloakSession session) { + return null; + } + + @Override + public void init(Config.Scope config) { + + } + + @Override + public void postInit(KeycloakSessionFactory factory) { + + } + + @Override + public void close() { + + } + + @Override + public UserFederationSyncResult syncAllUsers(KeycloakSessionFactory sessionFactory, final String realmId, final UserFederationProviderModel model) { + logger.info("Sync users not supported for this provider"); + return UserFederationSyncResult.empty(); + } + + @Override + public UserFederationSyncResult syncChangedUsers(KeycloakSessionFactory sessionFactory, final String realmId, final UserFederationProviderModel model, Date lastSync) { + logger.info("Sync users not supported for this provider"); + return UserFederationSyncResult.empty(); + } + + protected PAMAuthenticator createPAMAuthenticator(String username, String... factors) { + return new PAMAuthenticator(username, factors); + } + + @Override + public boolean isSupported() { + return Sssd.isAvailable(); + } +} \ No newline at end of file diff --git a/federation/sssd/src/main/java/org/keycloak/federation/sssd/api/Sssd.java b/federation/sssd/src/main/java/org/keycloak/federation/sssd/api/Sssd.java new file mode 100644 index 0000000000..065eb98842 --- /dev/null +++ b/federation/sssd/src/main/java/org/keycloak/federation/sssd/api/Sssd.java @@ -0,0 +1,134 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.keycloak.federation.sssd.api; + +import cx.ath.matthew.LibraryLoader; +import org.freedesktop.DBus; +import org.freedesktop.dbus.DBusConnection; +import org.freedesktop.dbus.Variant; +import org.freedesktop.dbus.exceptions.DBusException; +import org.freedesktop.sssd.infopipe.InfoPipe; +import org.freedesktop.sssd.infopipe.User; +import org.jboss.logging.Logger; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Vector; + +/** + * @author Bruno Oliveira + * @version $Revision: 1 $ + */ +public class Sssd { + + public static User user() { + return SingletonHolder.USER_OBJECT; + } + + public static InfoPipe infopipe() { + return SingletonHolder.INFOPIPE_OBJECT; + } + + + public static void disconnect() { + SingletonHolder.DBUS_CONNECTION.disconnect(); + } + + private String username; + private static final Logger logger = Logger.getLogger(Sssd.class); + + private Sssd() { + } + + public Sssd(String username) { + this.username = username; + } + + private static final class SingletonHolder { + private static InfoPipe INFOPIPE_OBJECT; + private static User USER_OBJECT; + private static DBusConnection DBUS_CONNECTION; + + static { + try { + DBUS_CONNECTION = DBusConnection.getConnection(DBusConnection.SYSTEM); + INFOPIPE_OBJECT = DBUS_CONNECTION.getRemoteObject(InfoPipe.BUSNAME, InfoPipe.OBJECTPATH, InfoPipe.class); + USER_OBJECT = DBUS_CONNECTION.getRemoteObject(InfoPipe.BUSNAME, User.OBJECTPATH, User.class); + } catch (DBusException e) { + logger.error("Failed to obtain D-Bus connection", e); + } + } + } + + public static String getRawAttribute(Variant variant) { + if (variant != null) { + Vector value = (Vector) variant.getValue(); + if (value.size() >= 1) { + return value.get(0).toString(); + } + } + return null; + } + + public Map getUserAttributes() { + String[] attr = {"mail", "givenname", "sn", "telephoneNumber"}; + Map attributes = null; + try { + InfoPipe infoPipe = infopipe(); + attributes = infoPipe.getUserAttributes(username, Arrays.asList(attr)); + } catch (Exception e) { + logger.error("Failed to retrieve user's attributes from SSSD", e); + } + + return attributes; + } + + public List getUserGroups() { + List userGroups = null; + try { + InfoPipe infoPipe = Sssd.infopipe(); + userGroups = infoPipe.getUserGroups(username); + } catch (Exception e) { + logger.error("Failed to retrieve user's groups from SSSD", e); + } + return userGroups; + } + + public static boolean isAvailable(){ + boolean sssdAvailable = false; + try { + if (LibraryLoader.load().succeed()) { + DBusConnection connection = DBusConnection.getConnection(DBusConnection.SYSTEM); + DBus dbus = connection.getRemoteObject(DBus.BUSNAME, DBus.OBJECTPATH, DBus.class); + sssdAvailable = Arrays.asList(dbus.ListNames()).contains(InfoPipe.BUSNAME); + if (!sssdAvailable) { + logger.debugv("SSSD is not available in your system. Federation provider will be disabled."); + } else { + sssdAvailable = true; + } + connection.disconnect(); + } else { + logger.debugv("libunix_dbus_java not found. Federation provider will be disabled."); + } + } catch (DBusException e) { + logger.error("Failed to check the status of SSSD", e); + } + return sssdAvailable; + } +} diff --git a/federation/sssd/src/main/java/org/keycloak/federation/sssd/impl/PAMAuthenticator.java b/federation/sssd/src/main/java/org/keycloak/federation/sssd/impl/PAMAuthenticator.java new file mode 100644 index 0000000000..f9822014c0 --- /dev/null +++ b/federation/sssd/src/main/java/org/keycloak/federation/sssd/impl/PAMAuthenticator.java @@ -0,0 +1,62 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.keycloak.federation.sssd.impl; + +import org.jboss.logging.Logger; +import org.jvnet.libpam.PAM; +import org.jvnet.libpam.PAMException; +import org.jvnet.libpam.UnixUser; + +/** + * PAMAuthenticator for Unix users + * + * @author Bruno Oliveira + * @version $Revision: 1 $ + */ +public class PAMAuthenticator { + + private static final String PAM_SERVICE = "keycloak"; + private static final Logger logger = Logger.getLogger(PAMAuthenticator.class); + private final String username; + private final String[] factors; + + public PAMAuthenticator(String username, String... factors) { + this.username = username; + this.factors = factors; + } + + /** + * Returns true if user was successfully authenticated against PAM + * + * @return UnixUser object if user was successfully authenticated + */ + public UnixUser authenticate() { + PAM pam = null; + UnixUser user = null; + try { + pam = new PAM(PAM_SERVICE); + user = pam.authenticate(username, factors); + } catch (PAMException e) { + logger.error("Authentication failed", e); + e.printStackTrace(); + } finally { + pam.dispose(); + } + return user; + } +} diff --git a/federation/sssd/src/main/resources/DBUS-JAVA-AUTHORS b/federation/sssd/src/main/resources/DBUS-JAVA-AUTHORS new file mode 100644 index 0000000000..9b7699ed2e --- /dev/null +++ b/federation/sssd/src/main/resources/DBUS-JAVA-AUTHORS @@ -0,0 +1,37 @@ +The D-Bus Java implementation was written by: + +Matthew Johnson + +Bug fixes/reports and other suggestions from: + +Remi Emonet +Simon McVittie +Dick Hollenbeck +Joshua Nichols +Ralf Kistner +Henrik Petander +Luigi Paioro +Roberto Francisco Arroyo Moreno +Steve Crane +Philippe Marschall +Daniel Machado +Anibal Sanchez +Jan Kümmel +Johannes Felten +Tom Walsh +Ed Wei +Sveinung Kvilhaugsvik +Hugues Moreau +Viktar Vauchkevich +Serkan Kaba +Adam Bennett +Frank Benoit +Gunnar Aastrand Grimnes + +The included Viewer application was originally written by: + +Peter Cox + +with patches from: + +Zsombor Gegesy diff --git a/federation/sssd/src/main/resources/DBUS-JAVA-LICENSE b/federation/sssd/src/main/resources/DBUS-JAVA-LICENSE new file mode 100644 index 0000000000..d651143606 --- /dev/null +++ b/federation/sssd/src/main/resources/DBUS-JAVA-LICENSE @@ -0,0 +1,680 @@ +The D-Bus Java implementation is licensed to you under your choice of the +Academic Free License version 2.1, or the GNU Lesser/Library General Public License +version 2. Both licenses are included here. Each source code file is marked +with the proper copyright information. + +The Academic Free License +v. 2.1 + +This Academic Free License (the "License") applies to any original work of +authorship (the "Original Work") whose owner (the "Licensor") has placed the +following notice immediately following the copyright notice for the Original +Work: + +Licensed under the Academic Free License version 2.1 + +1) Grant of Copyright License. Licensor hereby grants You a world-wide, +royalty-free, non-exclusive, perpetual, sublicenseable license to do the +following: + +a) to reproduce the Original Work in copies; + +b) to prepare derivative works ("Derivative Works") based upon the Original +Work; + +c) to distribute copies of the Original Work and Derivative Works to the +public; + +d) to perform the Original Work publicly; and + +e) to display the Original Work publicly. + +2) Grant of Patent License. Licensor hereby grants You a world-wide, +royalty-free, non-exclusive, perpetual, sublicenseable license, under patent +claims owned or controlled by the Licensor that are embodied in the Original +Work as furnished by the Licensor, to make, use, sell and offer for sale the +Original Work and Derivative Works. + +3) Grant of Source Code License. The term "Source Code" means the preferred +form of the Original Work for making modifications to it and all available +documentation describing how to modify the Original Work. Licensor hereby +agrees to provide a machine-readable copy of the Source Code of the Original +Work along with each copy of the Original Work that Licensor distributes. +Licensor reserves the right to satisfy this obligation by placing a +machine-readable copy of the Source Code in an information repository +reasonably calculated to permit inexpensive and convenient access by You for as +long as Licensor continues to distribute the Original Work, and by publishing +the address of that information repository in a notice immediately following +the copyright notice that applies to the Original Work. + +4) Exclusions From License Grant. Neither the names of Licensor, nor the names +of any contributors to the Original Work, nor any of their trademarks or +service marks, may be used to endorse or promote products derived from this +Original Work without express prior written permission of the Licensor. Nothing +in this License shall be deemed to grant any rights to trademarks, copyrights, +patents, trade secrets or any other intellectual property of Licensor except as +expressly stated herein. No patent license is granted to make, use, sell or +offer to sell embodiments of any patent claims other than the licensed claims +defined in Section 2. No right is granted to the trademarks of Licensor even if +such marks are included in the Original Work. Nothing in this License shall be +interpreted to prohibit Licensor from licensing under different terms from this +License any Original Work that Licensor otherwise would have a right to +license. + +5) This section intentionally omitted. + +6) Attribution Rights. You must retain, in the Source Code of any Derivative +Works that You create, all copyright, patent or trademark notices from the +Source Code of the Original Work, as well as any notices of licensing and any +descriptive text identified therein as an "Attribution Notice." You must cause +the Source Code for any Derivative Works that You create to carry a prominent +Attribution Notice reasonably calculated to inform recipients that You have +modified the Original Work. + +7) Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that +the copyright in and to the Original Work and the patent rights granted herein +by Licensor are owned by the Licensor or are sublicensed to You under the terms +of this License with the permission of the contributor(s) of those copyrights +and patent rights. Except as expressly stated in the immediately proceeding +sentence, the Original Work is provided under this License on an "AS IS" BASIS +and WITHOUT WARRANTY, either express or implied, including, without limitation, +the warranties of NON-INFRINGEMENT, MERCHANTABILITY or FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU. +This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No +license to Original Work is granted hereunder except under this disclaimer. + +8) Limitation of Liability. Under no circumstances and under no legal theory, +whether in tort (including negligence), contract, or otherwise, shall the +Licensor be liable to any person for any direct, indirect, special, incidental, +or consequential damages of any character arising as a result of this License +or the use of the Original Work including, without limitation, damages for loss +of goodwill, work stoppage, computer failure or malfunction, or any and all +other commercial damages or losses. This limitation of liability shall not +apply to liability for death or personal injury resulting from Licensor's +negligence to the extent applicable law prohibits such limitation. Some +jurisdictions do not allow the exclusion or limitation of incidental or +consequential damages, so this exclusion and limitation may not apply to You. + +9) Acceptance and Termination. If You distribute copies of the Original Work or +a Derivative Work, You must make a reasonable effort under the circumstances to +obtain the express assent of recipients to the terms of this License. Nothing +else but this License (or another written agreement between Licensor and You) +grants You permission to create Derivative Works based upon the Original Work +or to exercise any of the rights granted in Section 1 herein, and any attempt +to do so except under the terms of this License (or another written agreement +between Licensor and You) is expressly prohibited by U.S. copyright law, the +equivalent laws of other countries, and by international treaty. Therefore, by +exercising any of the rights granted to You in Section 1 herein, You indicate +Your acceptance of this License and all of its terms and conditions. + +10) Termination for Patent Action. This License shall terminate automatically +and You may no longer exercise any of the rights granted to You by this License +as of the date You commence an action, including a cross-claim or counterclaim, +against Licensor or any licensee alleging that the Original Work infringes a +patent. This termination provision shall not apply for an action alleging +patent infringement by combinations of the Original Work with other software or +hardware. + +11) Jurisdiction, Venue and Governing Law. Any action or suit relating to this +License may be brought only in the courts of a jurisdiction wherein the +Licensor resides or in which Licensor conducts its primary business, and under +the laws of that jurisdiction excluding its conflict-of-law provisions. The +application of the United Nations Convention on Contracts for the International +Sale of Goods is expressly excluded. Any use of the Original Work outside the +scope of this License or after its termination shall be subject to the +requirements and penalties of the U.S. Copyright Act, 17 U.S.C. § 101 et seq., +the equivalent laws of other countries, and international treaty. This section +shall survive the termination of this License. + +12) Attorneys Fees. In any action to enforce the terms of this License or +seeking damages relating thereto, the prevailing party shall be entitled to +recover its costs and expenses, including, without limitation, reasonable +attorneys' fees and costs incurred in connection with such action, including +any appeal of such action. This section shall survive the termination of this +License. + +13) Miscellaneous. This License represents the complete agreement concerning +the subject matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent necessary to +make it enforceable. + +14) Definition of "You" in This License. "You" throughout this License, whether +in upper or lower case, means an individual or a legal entity exercising rights +under, and complying with all of the terms of, this License. For legal +entities, "You" includes any entity that controls, is controlled by, or is +under common control with you. For purposes of this definition, "control" means +(i) the power, direct or indirect, to cause the direction or management of such +entity, whether by contract or otherwise, or (ii) ownership of fifty percent +(50%) or more of the outstanding shares, or (iii) beneficial ownership of such +entity. + +15) Right to Use. You may use the Original Work in all ways not otherwise +restricted or conditioned by this License or by law, and Licensor promises not +to interfere with or be responsible for such uses by You. + +This license is Copyright (C) 2003-2004 Lawrence E. Rosen. All rights reserved. +Permission is hereby granted to copy and distribute this license without +modification. This license may not be modified without the express written +permission of its copyright owner. + + +-- +END OF ACADEMIC FREE LICENSE. The following is intended to describe the +essential differences between the Academic Free License (AFL) version 1.0 and +other open source licenses: + +The Academic Free License is similar to the BSD, MIT, UoI/NCSA and Apache +licenses in many respects but it is intended to solve a few problems with those +licenses. + +* The AFL is written so as to make it clear what software is being +licensed (by the inclusion of a statement following the copyright notice in the +software). This way, the license functions better than a template license. The +BSD, MIT and UoI/NCSA licenses apply to unidentified software. + +* The AFL contains a complete copyright grant to the software. The BSD +and Apache licenses are vague and incomplete in that respect. + +* The AFL contains a complete patent grant to the software. The BSD, MIT, +UoI/NCSA and Apache licenses rely on an implied patent license and contain no +explicit patent grant. + +* The AFL makes it clear that no trademark rights are granted to the +licensor's trademarks. The Apache license contains such a provision, but the +BSD, MIT and UoI/NCSA licenses do not. + +* The AFL includes the warranty by the licensor that it either owns the +copyright or that it is distributing the software under a license. None of the +other licenses contain that warranty. All other warranties are disclaimed, as +is the case for the other licenses. + +* The AFL is itself copyrighted (with the right granted to copy and distribute +without modification). This ensures that the owner of the copyright to the +license will control changes. The Apache license contains a copyright notice, +but the BSD, MIT and UoI/NCSA licenses do not. + +-- +START OF GNU LIBRARY GENERAL PUBLIC LICENSE +-- + + GNU LIBRARY GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1991 Free Software Foundation, Inc. + 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the library GPL. It is + numbered 2 because it goes with version 2 of the ordinary GPL.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Library General Public License, applies to some +specially designated Free Software Foundation software, and to any +other libraries whose authors decide to use it. You can use it for +your libraries, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if +you distribute copies of the library, or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link a program with the library, you must provide +complete object files to the recipients so that they can relink them +with the library, after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + Our method of protecting your rights has two steps: (1) copyright +the library, and (2) offer you this license which gives you legal +permission to copy, distribute and/or modify the library. + + Also, for each distributor's protection, we want to make certain +that everyone understands that there is no warranty for this free +library. If the library is modified by someone else and passed on, we +want its recipients to know that what they have is not the original +version, so that any problems introduced by others will not reflect on +the original authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that companies distributing free +software will individually obtain patent licenses, thus in effect +transforming the program into proprietary software. To prevent this, +we have made it clear that any patent must be licensed for everyone's +free use or not licensed at all. + + Most GNU software, including some libraries, is covered by the ordinary +GNU General Public License, which was designed for utility programs. This +license, the GNU Library General Public License, applies to certain +designated libraries. This license is quite different from the ordinary +one; be sure to read it in full, and don't assume that anything in it is +the same as in the ordinary license. + + The reason we have a separate public license for some libraries is that +they blur the distinction we usually make between modifying or adding to a +program and simply using it. Linking a program with a library, without +changing the library, is in some sense simply using the library, and is +analogous to running a utility program or application program. However, in +a textual and legal sense, the linked executable is a combined work, a +derivative of the original library, and the ordinary General Public License +treats it as such. + + Because of this blurred distinction, using the ordinary General +Public License for libraries did not effectively promote software +sharing, because most developers did not use the libraries. We +concluded that weaker conditions might promote sharing better. + + However, unrestricted linking of non-free programs would deprive the +users of those programs of all benefit from the free status of the +libraries themselves. This Library General Public License is intended to +permit developers of non-free programs to use free libraries, while +preserving your freedom as a user of such programs to change the free +libraries that are incorporated in them. (We have not seen how to achieve +this as regards changes in header files, but we have achieved it as regards +changes in the actual functions of the Library.) The hope is that this +will lead to faster development of free libraries. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, while the latter only +works together with the library. + + Note that it is possible for a library to be covered by the ordinary +General Public License rather than by this special one. + + GNU LIBRARY GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library which +contains a notice placed by the copyright holder or other authorized +party saying it may be distributed under the terms of this Library +General Public License (also called "this License"). Each licensee is +addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also compile or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + c) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + d) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the source code distributed need not include anything that is normally +distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Library General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! diff --git a/federation/sssd/src/main/resources/META-INF/services/org.keycloak.models.UserFederationProviderFactory b/federation/sssd/src/main/resources/META-INF/services/org.keycloak.models.UserFederationProviderFactory new file mode 100644 index 0000000000..d06ece1bec --- /dev/null +++ b/federation/sssd/src/main/resources/META-INF/services/org.keycloak.models.UserFederationProviderFactory @@ -0,0 +1,18 @@ +# +# Copyright 2016 Red Hat, Inc. and/or its affiliates +# and other contributors as indicated by the @author tags. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +org.keycloak.federation.sssd.SSSDFederationProviderFactory \ No newline at end of file diff --git a/federation/sssd/src/main/resources/en_US.properties b/federation/sssd/src/main/resources/en_US.properties new file mode 100644 index 0000000000..31bc06c941 --- /dev/null +++ b/federation/sssd/src/main/resources/en_US.properties @@ -0,0 +1,82 @@ +#java-format +notBasicType=" is not a basic type" +notObjectProvidedByProcess=" is not an object provided by this process." +arrayOutOfBounds="Array index out of bounds, paofs={0}, pabuf.length={1}, buf.length={2}." +arrayMustNotExceed="Arrays must not exceed " +asyncCallNoReply="Async call has not had a reply" +busAddressBlank="Bus address is blank" +busAddressInvalid="Bus address is invalid: " +cannotWrapNullInVariant="Can't wrap Null in a Variant" +cannotWrapMultiValuedInVariant="Can't wrap a multi-valued type in a Variant: " +cannotWrapNoTypesInVariant="Can't wrap multiple or no types in a Variant: " +cannotWrapUnqualifiedVariant="Can't wrap {0} in an unqualified Variant ({1})." +cannotResolveSessionBusAddress="Cannot Resolve Session Bus Address" +cannotWatchSignalsWellKnownBussName="Cannot watch for signals based on well known bus name as source, only unique names." +cannotCreateClassFromSignal="Could not create class from signal " +interfaceToCastNotFound="Could not find an interface to cast to" +interfaceNotAllowedOutsidePackage="DBusInterfaces cannot be declared outside a package" +interfaceMustBeDefinedPackage="DBusInterfaces must be defined in a package." +disconnected="Disconnected" +errorExecutingMethod="Error Executing Method {0}.{1}: {2}" +errorDeserializingMessage="Error deserializing message: number of parameters didn't match receiving signature" +nonExportableParameterizedType="Exporting non-exportable parameterized type " +nonExportableType "Exporting non-exportable type " +errorAddSignalParameters="Failed to add signal parameters: " +errorAuth="Failed to auth" +connectionFailure="Failed to connect to bus " +contructDBusTypeFailure="Failed to construct D-Bus type: " +constructOutgoingMethodCallFailure="Failed to construct outgoing method call: " +createProxyExportFailure="Failed to create proxy object for {0} exported by {1}. Reason: {2}" +createProxyFailure="Failed to create proxy object for {0}; reason: {1}." +parseDBusTypeSignatureFailure="Failed to parse DBus type signature: " +parseDBusSignatureFailure="Failed to parse DBus type signature: {0} ({1})." +dbusRegistrationFailure="Failed to register bus name" +deSerializationFailure="Failure in de-serializing message: " +introspectInterfaceExceedCharacters="Introspected interface name exceeds 255 characters. Cannot export objects of type " +introspectMethodExceedCharacters="Introspected method name exceeds 255 characters. Cannot export objects with method " +introspectSignalExceedCharacters="Introspected signal name exceeds 255 characters. Cannot export objects with signals of type " +invalidBusType="Invalid Bus Type: " +invalidCommand="Invalid Command " +invalidBusName="Invalid bus name" +nullBusName="Invalid bus name: null" +invalidObjectPath="Invalid object path: " +nullObjectPath="Invalid object path: null" +invalidTypeMatchRule="Invalid type for match rule: " +mapParameters="Map must have 2 parameters" +messageFailedSend="Message Failed to Send: " +messageTypeUnsupported="Message type {0} unsupported" +multiValuedArrayNotPermitted="Multi-valued array types not permitted" +missingObjectPath="Must Specify an Object Path" +missingDestinationPathFunction="Must specify destination, path and function name to MethodCalls." +missingErrorName="Must specify error name to Errors." +missingPathInterfaceSignal="Must specify object path, interface and signal name to Signals." +notReplyWithSpecifiedTime="No reply within specified time" +missingTransport="No transport present" +notDBusInterface="Not A DBus Interface" +notDBusSignal="Not A DBus Signal" +convertionTypeNotExpected="Not An Expected Convertion type from {0} to {1}" +notConnected="Not Connected" +notPrimitiveType="Not a primitive type" +invalidDBusCode="Not a valid D-Bus type code: " +invalidWrapperType="Not a wrapper type" +invalidArray="Not an array" +objectNotExportedNoRemoteSpecified="Not an object exported by this connection and no remote specified" +notEnoughElementsToCreateCustomObject="Not enough elements to create custom object from serialized data ({0} < {1})." +objectAlreadyExported="Object already exported" +arraySentAsNonPrimitive="Primative array being sent as non-primative array." +protocolVersionUnsupported="Protocol version {0} is unsupported" +cannotIntrospectReturnType="Return type of Object[] cannot be introspected properly" +mustImplementDeserializeMethod="Serializable classes must implement a deserialize method" +mustSerializeNativeDBusTypes="Serializable classes must serialize to native DBus types" +signalsMustBeMemberOfClass="Signals must be declared as a member of a class implementing DBusInterface which is the member of a package." +spuriousReply="Spurious reply. No message with the given serial id was awaiting a reply." +utf8NotSupported="System does not support UTF-8 encoding" +methodDoesNotExist="The method `{0}.{1}' does not exist on this object." +unconvertableType="Trying to marshall to unconvertable type (from {0} to {1})." +transportReturnedEOF="Underlying transport returned EOF" +waitingFor="Waiting for: " +invalidReturnType="Wrong return type (failed to de-serialize correct types: {0} )" +voidReturnType="Wrong return type (got void, expected a value)" +tupleReturnType="Wrong return type (not expecting Tuple)" +unknownAddress="unknown address type " +isNotBetween="{0} is not between {1} and {2}." diff --git a/federation/sssd/src/test/java/cx/ath/matthew/unix/testclient.java b/federation/sssd/src/test/java/cx/ath/matthew/unix/testclient.java new file mode 100644 index 0000000000..2acef18b84 --- /dev/null +++ b/federation/sssd/src/test/java/cx/ath/matthew/unix/testclient.java @@ -0,0 +1,49 @@ +/* + * Java Unix Sockets Library + * + * Copyright (c) Matthew Johnson 2005 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * To Contact the author, please email src@matthew.ath.cx + * + */ + +package cx.ath.matthew.unix; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.PrintWriter; + +public class testclient { + public static void main(String args[]) throws IOException { + UnixSocket s = new UnixSocket(new UnixSocketAddress("testsock", true)); + OutputStream os = s.getOutputStream(); + PrintWriter o = new PrintWriter(os); + BufferedReader r = new BufferedReader(new InputStreamReader(System.in)); + String l; + while (null != (l = r.readLine())) { + byte[] buf = (l + "\n").getBytes(); + os.write(buf, 0, buf.length); + } + s.close(); + } +} diff --git a/federation/sssd/src/test/java/org/freedesktop/dbus/ProfileStruct.java b/federation/sssd/src/test/java/org/freedesktop/dbus/ProfileStruct.java new file mode 100644 index 0000000000..b86c137e54 --- /dev/null +++ b/federation/sssd/src/test/java/org/freedesktop/dbus/ProfileStruct.java @@ -0,0 +1,26 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; + +public final class ProfileStruct extends Struct { + @Position(0) + public final String a; + @Position(1) + public final UInt32 b; + @Position(2) + public final long c; + + public ProfileStruct(String a, UInt32 b, long c) { + this.a = a; + this.b = b; + this.c = c; + } +} diff --git a/federation/sssd/src/test/java/org/freedesktop/dbus/Profiler.java b/federation/sssd/src/test/java/org/freedesktop/dbus/Profiler.java new file mode 100644 index 0000000000..cd549d5de8 --- /dev/null +++ b/federation/sssd/src/test/java/org/freedesktop/dbus/Profiler.java @@ -0,0 +1,44 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; + +import org.freedesktop.dbus.exceptions.DBusException; + +import java.util.List; +import java.util.Map; + +public interface Profiler extends DBusInterface { + public class ProfileSignal extends DBusSignal { + public ProfileSignal(String path) throws DBusException { + super(path); + } + } + + public void array(int[] v); + + public void stringarray(String[] v); + + public void map(Map m); + + public void list(List l); + + public void bytes(byte[] b); + + public void struct(ProfileStruct ps); + + public void string(String s); + + public void NoReply(); + + public void Pong(); +} + + diff --git a/federation/sssd/src/test/java/org/freedesktop/dbus/ProfilerInstance.java b/federation/sssd/src/test/java/org/freedesktop/dbus/ProfilerInstance.java new file mode 100644 index 0000000000..530901f08b --- /dev/null +++ b/federation/sssd/src/test/java/org/freedesktop/dbus/ProfilerInstance.java @@ -0,0 +1,56 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; + +import java.util.List; +import java.util.Map; + +public class ProfilerInstance implements Profiler { + public boolean isRemote() { + return false; + } + + public void array(int[] v) { + return; + } + + public void stringarray(String[] v) { + return; + } + + public void map(Map m) { + return; + } + + public void list(List l) { + return; + } + + public void bytes(byte[] b) { + return; + } + + public void struct(ProfileStruct ps) { + return; + } + + public void string(String s) { + return; + } + + public void NoReply() { + return; + } + + public void Pong() { + return; + } +} diff --git a/federation/sssd/src/test/java/org/freedesktop/dbus/TestException.java b/federation/sssd/src/test/java/org/freedesktop/dbus/TestException.java new file mode 100644 index 0000000000..98335c8a14 --- /dev/null +++ b/federation/sssd/src/test/java/org/freedesktop/dbus/TestException.java @@ -0,0 +1,22 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; + +import org.freedesktop.DBus.Description; +import org.freedesktop.dbus.exceptions.DBusExecutionException; + +@Description("A test exception to throw over DBus") +@SuppressWarnings("serial") +public class TestException extends DBusExecutionException { + public TestException(String message) { + super(message); + } +} diff --git a/federation/sssd/src/test/java/org/freedesktop/dbus/TestNewInterface.java b/federation/sssd/src/test/java/org/freedesktop/dbus/TestNewInterface.java new file mode 100644 index 0000000000..63957878a7 --- /dev/null +++ b/federation/sssd/src/test/java/org/freedesktop/dbus/TestNewInterface.java @@ -0,0 +1,24 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; + +import org.freedesktop.DBus.Description; + +/** + * A sample remote interface which exports one method. + */ +public interface TestNewInterface extends DBusInterface { + /** + * A simple method with no parameters which returns a String + */ + @Description("Simple test method") + public String getName(); +} diff --git a/federation/sssd/src/test/java/org/freedesktop/dbus/TestRemoteInterface.java b/federation/sssd/src/test/java/org/freedesktop/dbus/TestRemoteInterface.java new file mode 100644 index 0000000000..6fec592a2a --- /dev/null +++ b/federation/sssd/src/test/java/org/freedesktop/dbus/TestRemoteInterface.java @@ -0,0 +1,68 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; + +import org.freedesktop.DBus.Description; +import org.freedesktop.DBus.Method; + +import java.lang.reflect.Type; +import java.util.List; +import java.util.Map; + +/** + * A sample remote interface which exports one method. + */ +public interface TestRemoteInterface extends DBusInterface { + /** + * A simple method with no parameters which returns a String + */ + @Description("Simple test method") + public String getName(); + + public String getNameAndThrow(); + + @Description("Test of nested maps") + public int frobnicate(List n, Map> m, T v); + + @Description("Throws a TestException when called") + public void throwme() throws TestException; + + @Description("Waits then doesn't return") + @Method.NoReply() + public void waitawhile(); + + @Description("Interface-overloaded method") + public int overload(); + + @Description("Testing Type Signatures") + public void sig(Type[] s); + + @Description("Testing object paths as Path objects") + public void newpathtest(Path p); + + @Description("Testing the float type") + public float testfloat(float[] f); + + @Description("Testing structs of structs") + public int[][] teststructstruct(TestStruct3 in); + + @Description("Regression test for #13291") + public void reg13291(byte[] as, byte[] bs); + + public Map svm(); + + /* test lots of things involving Path */ + public Path pathrv(Path a); + + public List pathlistrv(List a); + + public Map pathmaprv(Map a); +} diff --git a/federation/sssd/src/test/java/org/freedesktop/dbus/TestRemoteInterface2.java b/federation/sssd/src/test/java/org/freedesktop/dbus/TestRemoteInterface2.java new file mode 100644 index 0000000000..7039fe13ae --- /dev/null +++ b/federation/sssd/src/test/java/org/freedesktop/dbus/TestRemoteInterface2.java @@ -0,0 +1,62 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; + +import org.freedesktop.DBus.Description; + +import java.util.List; + +@Description("An example remote interface") +@DBusInterfaceName("org.freedesktop.dbus.test.AlternateTestInterface") +public interface TestRemoteInterface2 extends DBusInterface { + @Description("Test multiple return values and implicit variant parameters.") + public TestTuple, Boolean> show(A in); + + @Description("Test passing structs and explicit variants, returning implicit variants") + public T dostuff(TestStruct foo); + + @Description("Test arrays, boxed arrays and lists.") + public List sampleArray(List l, Integer[] is, long[] ls); + + @Description("Test passing objects as object paths.") + public DBusInterface getThis(DBusInterface t); + + @Description("Test bools work") + @DBusMemberName("checkbool") + public boolean check(); + + @Description("Test Serializable Object") + public TestSerializable testSerializable(byte b, TestSerializable s, int i); + + @Description("Call another method on itself from within a call") + public String recursionTest(); + + @Description("Parameter-overloaded method (string)") + public int overload(String s); + + @Description("Parameter-overloaded method (byte)") + public int overload(byte b); + + @Description("Parameter-overloaded method (void)") + public int overload(); + + @Description("Nested List Check") + public List> checklist(List> lli); + + @Description("Get new objects as object paths.") + public TestNewInterface getNew(); + + @Description("Test Complex Variants") + public void complexv(Variant v); + + @Description("Test Introspect on a different interface") + public String Introspect(); +} diff --git a/federation/sssd/src/test/java/org/freedesktop/dbus/TestSerializable.java b/federation/sssd/src/test/java/org/freedesktop/dbus/TestSerializable.java new file mode 100644 index 0000000000..0a8275aae8 --- /dev/null +++ b/federation/sssd/src/test/java/org/freedesktop/dbus/TestSerializable.java @@ -0,0 +1,57 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; + +import org.freedesktop.dbus.exceptions.DBusException; + +import java.util.List; +import java.util.Vector; + +public class TestSerializable implements DBusSerializable { + private int a; + private String b; + private Vector c; + + public TestSerializable(int a, A b, Vector c) { + this.a = a; + this.b = b.toString(); + this.c = c; + } + + public TestSerializable() { + } + + public void deserialize(int a, String b, List c) { + this.a = a; + this.b = b; + this.c = new Vector(c); + } + + public Object[] serialize() throws DBusException { + return new Object[]{a, b, c}; + } + + public int getInt() { + return a; + } + + public String getString() { + return b; + } + + public Vector getVector() { + return c; + } + + public String toString() { + return "TestSerializable{" + a + "," + b + "," + c + "}"; + } +} diff --git a/federation/sssd/src/test/java/org/freedesktop/dbus/TestSignalInterface.java b/federation/sssd/src/test/java/org/freedesktop/dbus/TestSignalInterface.java new file mode 100644 index 0000000000..518a5db47d --- /dev/null +++ b/federation/sssd/src/test/java/org/freedesktop/dbus/TestSignalInterface.java @@ -0,0 +1,89 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; + +import org.freedesktop.DBus.Description; +import org.freedesktop.dbus.exceptions.DBusException; + +import java.util.List; +import java.util.Map; + +/** + * A sample signal with two parameters + */ +@Description("Test interface containing signals") +public interface TestSignalInterface extends DBusInterface { + @Description("Test basic signal") + public static class TestSignal extends DBusSignal { + public final String value; + public final UInt32 number; + + /** + * Create a signal. + */ + public TestSignal(String path, String value, UInt32 number) throws DBusException { + super(path, value, number); + this.value = value; + this.number = number; + } + } + + public static class StringSignal extends DBusSignal { + public final String aoeu; + + public StringSignal(String path, String aoeu) throws DBusException { + super(path, aoeu); + this.aoeu = aoeu; + } + } + + public static class EmptySignal extends DBusSignal { + public EmptySignal(String path) throws DBusException { + super(path); + } + } + + @Description("Test signal with arrays") + public static class TestArraySignal extends DBusSignal { + public final List v; + public final Map m; + + public TestArraySignal(String path, List v, Map m) throws DBusException { + super(path, v, m); + this.v = v; + this.m = m; + } + } + + @Description("Test signal sending an object path") + @DBusMemberName("TestSignalObject") + public static class TestObjectSignal extends DBusSignal { + public final DBusInterface otherpath; + + public TestObjectSignal(String path, DBusInterface otherpath) throws DBusException { + super(path, otherpath); + this.otherpath = otherpath; + } + } + + public static class TestPathSignal extends DBusSignal { + public final Path otherpath; + public final List pathlist; + public final Map pathmap; + + public TestPathSignal(String path, Path otherpath, List pathlist, Map pathmap) throws DBusException { + super(path, otherpath, pathlist, pathmap); + this.otherpath = otherpath; + this.pathlist = pathlist; + this.pathmap = pathmap; + } + } +} diff --git a/federation/sssd/src/test/java/org/freedesktop/dbus/TestSignalInterface2.java b/federation/sssd/src/test/java/org/freedesktop/dbus/TestSignalInterface2.java new file mode 100644 index 0000000000..9324b40b12 --- /dev/null +++ b/federation/sssd/src/test/java/org/freedesktop/dbus/TestSignalInterface2.java @@ -0,0 +1,36 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; + +import org.freedesktop.DBus.Description; +import org.freedesktop.dbus.exceptions.DBusException; + +/** + * A sample signal with two parameters + */ +@Description("Test interface containing signals") +@DBusInterfaceName("some.other.interface.Name") +public interface TestSignalInterface2 extends DBusInterface { + @Description("Test basic signal") + public static class TestRenamedSignal extends DBusSignal { + public final String value; + public final UInt32 number; + + /** + * Create a signal. + */ + public TestRenamedSignal(String path, String value, UInt32 number) throws DBusException { + super(path, value, number); + this.value = value; + this.number = number; + } + } +} diff --git a/federation/sssd/src/test/java/org/freedesktop/dbus/TestStruct.java b/federation/sssd/src/test/java/org/freedesktop/dbus/TestStruct.java new file mode 100644 index 0000000000..09f42fca89 --- /dev/null +++ b/federation/sssd/src/test/java/org/freedesktop/dbus/TestStruct.java @@ -0,0 +1,26 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; + +public final class TestStruct extends Struct { + @Position(0) + public final String a; + @Position(1) + public final UInt32 b; + @Position(2) + public final Variant c; + + public TestStruct(String a, UInt32 b, Variant c) { + this.a = a; + this.b = b; + this.c = c; + } +} diff --git a/federation/sssd/src/test/java/org/freedesktop/dbus/TestStruct2.java b/federation/sssd/src/test/java/org/freedesktop/dbus/TestStruct2.java new file mode 100644 index 0000000000..efd0d878b2 --- /dev/null +++ b/federation/sssd/src/test/java/org/freedesktop/dbus/TestStruct2.java @@ -0,0 +1,27 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; + +import org.freedesktop.dbus.exceptions.DBusException; + +import java.util.List; + +public final class TestStruct2 extends Struct { + @Position(0) + public final List a; + @Position(1) + public final Variant b; + + public TestStruct2(List a, Variant b) throws DBusException { + this.a = a; + this.b = b; + } +} diff --git a/federation/sssd/src/test/java/org/freedesktop/dbus/TestStruct3.java b/federation/sssd/src/test/java/org/freedesktop/dbus/TestStruct3.java new file mode 100644 index 0000000000..d457053630 --- /dev/null +++ b/federation/sssd/src/test/java/org/freedesktop/dbus/TestStruct3.java @@ -0,0 +1,27 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; + +import org.freedesktop.dbus.exceptions.DBusException; + +import java.util.List; + +public final class TestStruct3 extends Struct { + @Position(0) + public final TestStruct2 a; + @Position(1) + public final List> b; + + public TestStruct3(TestStruct2 a, List> b) throws DBusException { + this.a = a; + this.b = b; + } +} diff --git a/federation/sssd/src/test/java/org/freedesktop/dbus/TestTuple.java b/federation/sssd/src/test/java/org/freedesktop/dbus/TestTuple.java new file mode 100644 index 0000000000..4adb0d8643 --- /dev/null +++ b/federation/sssd/src/test/java/org/freedesktop/dbus/TestTuple.java @@ -0,0 +1,26 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; + +public final class TestTuple extends Tuple { + @Position(0) + public final A a; + @Position(1) + public final B b; + @Position(2) + public final C c; + + public TestTuple(A a, B b, C c) { + this.a = a; + this.b = b; + this.c = c; + } +} diff --git a/federation/sssd/src/test/java/org/freedesktop/dbus/TwoPartInterface.java b/federation/sssd/src/test/java/org/freedesktop/dbus/TwoPartInterface.java new file mode 100644 index 0000000000..242dcb2fce --- /dev/null +++ b/federation/sssd/src/test/java/org/freedesktop/dbus/TwoPartInterface.java @@ -0,0 +1,26 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; + +import org.freedesktop.dbus.exceptions.DBusException; + +public interface TwoPartInterface extends DBusInterface { + public TwoPartObject getNew(); + + public class TwoPartSignal extends DBusSignal { + public final TwoPartObject o; + + public TwoPartSignal(String path, TwoPartObject o) throws DBusException { + super(path, o); + this.o = o; + } + } +} diff --git a/federation/sssd/src/test/java/org/freedesktop/dbus/TwoPartObject.java b/federation/sssd/src/test/java/org/freedesktop/dbus/TwoPartObject.java new file mode 100644 index 0000000000..8f03bbc7ff --- /dev/null +++ b/federation/sssd/src/test/java/org/freedesktop/dbus/TwoPartObject.java @@ -0,0 +1,15 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; + +public interface TwoPartObject extends DBusInterface { + public String getName(); +} diff --git a/federation/sssd/src/test/java/org/freedesktop/dbus/cross_test_client.java b/federation/sssd/src/test/java/org/freedesktop/dbus/cross_test_client.java new file mode 100644 index 0000000000..b71033dbf0 --- /dev/null +++ b/federation/sssd/src/test/java/org/freedesktop/dbus/cross_test_client.java @@ -0,0 +1,511 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; + +import cx.ath.matthew.debug.Debug; +import org.freedesktop.DBus; +import org.freedesktop.dbus.exceptions.DBusException; +import org.freedesktop.dbus.exceptions.DBusExecutionException; +import org.freedesktop.dbus.types.DBusMapType; + +import java.lang.reflect.Array; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.Set; +import java.util.TreeSet; +import java.util.Vector; + +public class cross_test_client implements DBus.Binding.TestClient, DBusSigHandler { + private DBusConnection conn; + private static Set passed = new TreeSet(); + private static Map> failed = new HashMap>(); + private static cross_test_client ctc; + + static { + List l = new Vector(); + l.add("Signal never arrived"); + failed.put("org.freedesktop.DBus.Binding.TestSignals.Triggered", l); + l = new Vector(); + l.add("Method never called"); + failed.put("org.freedesktop.DBus.Binding.TestClient.Response", l); + } + + public cross_test_client(DBusConnection conn) { + this.conn = conn; + } + + public boolean isRemote() { + return false; + } + + public void handle(DBus.Binding.TestSignals.Triggered t) { + failed.remove("org.freedesktop.DBus.Binding.TestSignals.Triggered"); + if (new UInt64(21389479283L).equals(t.a) && "/Test".equals(t.getPath())) + pass("org.freedesktop.DBus.Binding.TestSignals.Triggered"); + else if (!new UInt64(21389479283L).equals(t.a)) + fail("org.freedesktop.DBus.Binding.TestSignals.Triggered", "Incorrect signal content; expected 21389479283 got " + t.a); + else if (!"/Test".equals(t.getPath())) + fail("org.freedesktop.DBus.Binding.TestSignals.Triggered", "Incorrect signal source object; expected /Test got " + t.getPath()); + } + + public void Response(UInt16 a, double b) { + failed.remove("org.freedesktop.DBus.Binding.TestClient.Response"); + if (a.equals(new UInt16(15)) && (b == 12.5)) + pass("org.freedesktop.DBus.Binding.TestClient.Response"); + else + fail("org.freedesktop.DBus.Binding.TestClient.Response", "Incorrect parameters; expected 15, 12.5 got " + a + ", " + b); + } + + public static void pass(String test) { + passed.add(test.replaceAll("[$]", "")); + } + + public static void fail(String test, String reason) { + test = test.replaceAll("[$]", ""); + List reasons = failed.get(test); + if (null == reasons) { + reasons = new Vector(); + failed.put(test, reasons); + } + reasons.add(reason); + } + + @SuppressWarnings("unchecked") + public static void test(Class iface, Object proxy, String method, Object rv, Object... parameters) { + try { + Method[] ms = iface.getMethods(); + Method m = null; + for (Method t : ms) { + if (t.getName().equals(method)) + m = t; + } + Object o = m.invoke(proxy, parameters); + + String msg = "Incorrect return value; sent ( "; + if (null != parameters) + for (Object po : parameters) + if (null != po) + msg += collapseArray(po) + ","; + msg = msg.replaceAll(".$", ");"); + msg += " expected " + collapseArray(rv) + " got " + collapseArray(o); + + if (null != rv && rv.getClass().isArray()) { + compareArray(iface.getName() + "" + method, rv, o); + } else if (rv instanceof Map) { + if (o instanceof Map) { + Map a = (Map) o; + Map b = (Map) rv; + if (a.keySet().size() != b.keySet().size()) { + fail(iface.getName() + "" + method, msg); + } else for (Object k : a.keySet()) + if (a.get(k) instanceof List) { + if (b.get(k) instanceof List) + if (setCompareLists((List) a.get(k), (List) b.get(k))) + ; + else + fail(iface.getName() + "" + method, msg); + else + fail(iface.getName() + "" + method, msg); + } else if (!a.get(k).equals(b.get(k))) { + fail(iface.getName() + "" + method, msg); + return; + } + pass(iface.getName() + "" + method); + } else + fail(iface.getName() + "" + method, msg); + } else { + if (o == rv || (o != null && o.equals(rv))) + pass(iface.getName() + "" + method); + else + fail(iface.getName() + "" + method, msg); + } + } catch (DBusExecutionException DBEe) { + DBEe.printStackTrace(); + fail(iface.getName() + "" + method, "Error occurred during execution: " + DBEe.getClass().getName() + " " + DBEe.getMessage()); + } catch (InvocationTargetException ITe) { + ITe.printStackTrace(); + fail(iface.getName() + "" + method, "Error occurred during execution: " + ITe.getCause().getClass().getName() + " " + ITe.getCause().getMessage()); + } catch (Exception e) { + e.printStackTrace(); + fail(iface.getName() + "" + method, "Error occurred during execution: " + e.getClass().getName() + " " + e.getMessage()); + } + } + + @SuppressWarnings("unchecked") + public static String collapseArray(Object array) { + if (null == array) return "null"; + if (array.getClass().isArray()) { + String s = "{ "; + for (int i = 0; i < Array.getLength(array); i++) + s += collapseArray(Array.get(array, i)) + ","; + s = s.replaceAll(".$", " }"); + return s; + } else if (array instanceof List) { + String s = "{ "; + for (Object o : (List) array) + s += collapseArray(o) + ","; + s = s.replaceAll(".$", " }"); + return s; + } else if (array instanceof Map) { + String s = "{ "; + for (Object o : ((Map) array).keySet()) + s += collapseArray(o) + " => " + collapseArray(((Map) array).get(o)) + ","; + s = s.replaceAll(".$", " }"); + return s; + } else return array.toString(); + } + + public static boolean setCompareLists(List a, List b) { + if (a.size() != b.size()) return false; + for (Object v : a) + if (!b.contains(v)) return false; + return true; + } + + @SuppressWarnings("unchecked") + public static List> PrimitizeRecurse(Object a, Type t) { + List> vs = new Vector>(); + if (t instanceof ParameterizedType) { + Class c = (Class) ((ParameterizedType) t).getRawType(); + if (List.class.isAssignableFrom(c)) { + Object os; + if (a instanceof List) + os = ((List) a).toArray(); + else + os = a; + Type[] ts = ((ParameterizedType) t).getActualTypeArguments(); + for (int i = 0; i < Array.getLength(os); i++) + vs.addAll(PrimitizeRecurse(Array.get(os, i), ts[0])); + } else if (Map.class.isAssignableFrom(c)) { + Object[] os = ((Map) a).keySet().toArray(); + Object[] ks = ((Map) a).values().toArray(); + Type[] ts = ((ParameterizedType) t).getActualTypeArguments(); + for (int i = 0; i < ks.length; i++) + vs.addAll(PrimitizeRecurse(ks[i], ts[0])); + for (int i = 0; i < os.length; i++) + vs.addAll(PrimitizeRecurse(os[i], ts[1])); + } else if (Struct.class.isAssignableFrom(c)) { + Object[] os = ((Struct) a).getParameters(); + Type[] ts = ((ParameterizedType) t).getActualTypeArguments(); + for (int i = 0; i < os.length; i++) + vs.addAll(PrimitizeRecurse(os[i], ts[i])); + + } else if (Variant.class.isAssignableFrom(c)) { + vs.addAll(PrimitizeRecurse(((Variant) a).getValue(), ((Variant) a).getType())); + } + } else if (Variant.class.isAssignableFrom((Class) t)) + vs.addAll(PrimitizeRecurse(((Variant) a).getValue(), ((Variant) a).getType())); + else if (t instanceof Class && ((Class) t).isArray()) { + Type t2 = ((Class) t).getComponentType(); + for (int i = 0; i < Array.getLength(a); i++) + vs.addAll(PrimitizeRecurse(Array.get(a, i), t2)); + } else vs.add(new Variant(a)); + + return vs; + } + + @SuppressWarnings("unchecked") + public static List> Primitize(Variant a) { + return PrimitizeRecurse(a.getValue(), a.getType()); + } + + @SuppressWarnings("unchecked") + public static void primitizeTest(DBus.Binding.Tests tests, Object input) { + Variant in = new Variant(input); + List> vs = Primitize(in); + List> res; + + try { + + res = tests.Primitize(in); + if (setCompareLists(res, vs)) + pass("org.freedesktop.DBus.Binding.Tests.Primitize"); + else + fail("org.freedesktop.DBus.Binding.Tests.Primitize", "Wrong Return Value; expected " + collapseArray(vs) + " got " + collapseArray(res)); + + } catch (Exception e) { + if (Debug.debug) Debug.print(e); + fail("org.freedesktop.DBus.Binding.Tests.Primitize", "Exception occurred during test: (" + e.getClass().getName() + ") " + e.getMessage()); + } + } + + public static void doTests(DBus.Peer peer, DBus.Introspectable intro, DBus.Introspectable rootintro, DBus.Binding.Tests tests, DBus.Binding.SingleTests singletests) { + Random r = new Random(); + int i; + test(DBus.Peer.class, peer, "Ping", null); + + try { + if (intro.Introspect().startsWith("(new Integer(1)), new Variant(new Integer(1))); + test(DBus.Binding.Tests.class, tests, "Identity", new Variant("Hello"), new Variant("Hello")); + + test(DBus.Binding.Tests.class, tests, "IdentityBool", false, false); + test(DBus.Binding.Tests.class, tests, "IdentityBool", true, true); + + test(DBus.Binding.Tests.class, tests, "Invert", false, true); + test(DBus.Binding.Tests.class, tests, "Invert", true, false); + + test(DBus.Binding.Tests.class, tests, "IdentityByte", (byte) 0, (byte) 0); + test(DBus.Binding.Tests.class, tests, "IdentityByte", (byte) 1, (byte) 1); + test(DBus.Binding.Tests.class, tests, "IdentityByte", (byte) -1, (byte) -1); + test(DBus.Binding.Tests.class, tests, "IdentityByte", Byte.MAX_VALUE, Byte.MAX_VALUE); + test(DBus.Binding.Tests.class, tests, "IdentityByte", Byte.MIN_VALUE, Byte.MIN_VALUE); + i = r.nextInt(); + test(DBus.Binding.Tests.class, tests, "IdentityByte", (byte) i, (byte) i); + + test(DBus.Binding.Tests.class, tests, "IdentityInt16", (short) 0, (short) 0); + test(DBus.Binding.Tests.class, tests, "IdentityInt16", (short) 1, (short) 1); + test(DBus.Binding.Tests.class, tests, "IdentityInt16", (short) -1, (short) -1); + test(DBus.Binding.Tests.class, tests, "IdentityInt16", Short.MAX_VALUE, Short.MAX_VALUE); + test(DBus.Binding.Tests.class, tests, "IdentityInt16", Short.MIN_VALUE, Short.MIN_VALUE); + i = r.nextInt(); + test(DBus.Binding.Tests.class, tests, "IdentityInt16", (short) i, (short) i); + + test(DBus.Binding.Tests.class, tests, "IdentityInt32", 0, 0); + test(DBus.Binding.Tests.class, tests, "IdentityInt32", 1, 1); + test(DBus.Binding.Tests.class, tests, "IdentityInt32", -1, -1); + test(DBus.Binding.Tests.class, tests, "IdentityInt32", Integer.MAX_VALUE, Integer.MAX_VALUE); + test(DBus.Binding.Tests.class, tests, "IdentityInt32", Integer.MIN_VALUE, Integer.MIN_VALUE); + i = r.nextInt(); + test(DBus.Binding.Tests.class, tests, "IdentityInt32", i, i); + + + test(DBus.Binding.Tests.class, tests, "IdentityInt64", (long) 0, (long) 0); + test(DBus.Binding.Tests.class, tests, "IdentityInt64", (long) 1, (long) 1); + test(DBus.Binding.Tests.class, tests, "IdentityInt64", (long) -1, (long) -1); + test(DBus.Binding.Tests.class, tests, "IdentityInt64", Long.MAX_VALUE, Long.MAX_VALUE); + test(DBus.Binding.Tests.class, tests, "IdentityInt64", Long.MIN_VALUE, Long.MIN_VALUE); + i = r.nextInt(); + test(DBus.Binding.Tests.class, tests, "IdentityInt64", (long) i, (long) i); + + test(DBus.Binding.Tests.class, tests, "IdentityUInt16", new UInt16(0), new UInt16(0)); + test(DBus.Binding.Tests.class, tests, "IdentityUInt16", new UInt16(1), new UInt16(1)); + test(DBus.Binding.Tests.class, tests, "IdentityUInt16", new UInt16(UInt16.MAX_VALUE), new UInt16(UInt16.MAX_VALUE)); + test(DBus.Binding.Tests.class, tests, "IdentityUInt16", new UInt16(UInt16.MIN_VALUE), new UInt16(UInt16.MIN_VALUE)); + i = r.nextInt(); + i = i > 0 ? i : -i; + test(DBus.Binding.Tests.class, tests, "IdentityUInt16", new UInt16(i % UInt16.MAX_VALUE), new UInt16(i % UInt16.MAX_VALUE)); + + test(DBus.Binding.Tests.class, tests, "IdentityUInt32", new UInt32(0), new UInt32(0)); + test(DBus.Binding.Tests.class, tests, "IdentityUInt32", new UInt32(1), new UInt32(1)); + test(DBus.Binding.Tests.class, tests, "IdentityUInt32", new UInt32(UInt32.MAX_VALUE), new UInt32(UInt32.MAX_VALUE)); + test(DBus.Binding.Tests.class, tests, "IdentityUInt32", new UInt32(UInt32.MIN_VALUE), new UInt32(UInt32.MIN_VALUE)); + i = r.nextInt(); + i = i > 0 ? i : -i; + test(DBus.Binding.Tests.class, tests, "IdentityUInt32", new UInt32(i % UInt32.MAX_VALUE), new UInt32(i % UInt32.MAX_VALUE)); + + test(DBus.Binding.Tests.class, tests, "IdentityUInt64", new UInt64(0), new UInt64(0)); + test(DBus.Binding.Tests.class, tests, "IdentityUInt64", new UInt64(1), new UInt64(1)); + test(DBus.Binding.Tests.class, tests, "IdentityUInt64", new UInt64(UInt64.MAX_LONG_VALUE), new UInt64(UInt64.MAX_LONG_VALUE)); + test(DBus.Binding.Tests.class, tests, "IdentityUInt64", new UInt64(UInt64.MAX_BIG_VALUE), new UInt64(UInt64.MAX_BIG_VALUE)); + test(DBus.Binding.Tests.class, tests, "IdentityUInt64", new UInt64(UInt64.MIN_VALUE), new UInt64(UInt64.MIN_VALUE)); + i = r.nextInt(); + i = i > 0 ? i : -i; + test(DBus.Binding.Tests.class, tests, "IdentityUInt64", new UInt64(i % UInt64.MAX_LONG_VALUE), new UInt64(i % UInt64.MAX_LONG_VALUE)); + + test(DBus.Binding.Tests.class, tests, "IdentityDouble", 0.0, 0.0); + test(DBus.Binding.Tests.class, tests, "IdentityDouble", 1.0, 1.0); + test(DBus.Binding.Tests.class, tests, "IdentityDouble", -1.0, -1.0); + test(DBus.Binding.Tests.class, tests, "IdentityDouble", Double.MAX_VALUE, Double.MAX_VALUE); + test(DBus.Binding.Tests.class, tests, "IdentityDouble", Double.MIN_VALUE, Double.MIN_VALUE); + i = r.nextInt(); + test(DBus.Binding.Tests.class, tests, "IdentityDouble", (double) i, (double) i); + + test(DBus.Binding.Tests.class, tests, "IdentityString", "", ""); + test(DBus.Binding.Tests.class, tests, "IdentityString", "The Quick Brown Fox Jumped Over The Lazy Dog", "The Quick Brown Fox Jumped Over The Lazy Dog"); + test(DBus.Binding.Tests.class, tests, "IdentityString", "ひらがなゲーム - かなぶん", "ひらがなゲーム - かなぶん"); + + testArray(DBus.Binding.Tests.class, tests, "IdentityBoolArray", Boolean.TYPE, null); + testArray(DBus.Binding.Tests.class, tests, "IdentityByteArray", Byte.TYPE, null); + testArray(DBus.Binding.Tests.class, tests, "IdentityInt16Array", Short.TYPE, null); + testArray(DBus.Binding.Tests.class, tests, "IdentityInt32Array", Integer.TYPE, null); + testArray(DBus.Binding.Tests.class, tests, "IdentityInt64Array", Long.TYPE, null); + testArray(DBus.Binding.Tests.class, tests, "IdentityDoubleArray", Double.TYPE, null); + + testArray(DBus.Binding.Tests.class, tests, "IdentityArray", Variant.class, new Variant("aoeu")); + testArray(DBus.Binding.Tests.class, tests, "IdentityUInt16Array", UInt16.class, new UInt16(12)); + testArray(DBus.Binding.Tests.class, tests, "IdentityUInt32Array", UInt32.class, new UInt32(190)); + testArray(DBus.Binding.Tests.class, tests, "IdentityUInt64Array", UInt64.class, new UInt64(103948)); + testArray(DBus.Binding.Tests.class, tests, "IdentityStringArray", String.class, "asdf"); + + int[] is = new int[0]; + test(DBus.Binding.Tests.class, tests, "Sum", 0L, is); + r = new Random(); + int len = (r.nextInt() % 100) + 15; + len = (len < 0 ? -len : len) + 15; + is = new int[len]; + long result = 0; + for (i = 0; i < len; i++) { + is[i] = r.nextInt(); + result += is[i]; + } + test(DBus.Binding.Tests.class, tests, "Sum", result, is); + + byte[] bs = new byte[0]; + test(DBus.Binding.SingleTests.class, singletests, "Sum", new UInt32(0), bs); + len = (r.nextInt() % 100); + len = (len < 0 ? -len : len) + 15; + bs = new byte[len]; + int res = 0; + for (i = 0; i < len; i++) { + bs[i] = (byte) r.nextInt(); + res += (bs[i] < 0 ? bs[i] + 256 : bs[i]); + } + test(DBus.Binding.SingleTests.class, singletests, "Sum", new UInt32(res % (UInt32.MAX_VALUE + 1)), bs); + + test(DBus.Binding.Tests.class, tests, "DeStruct", new DBus.Binding.Triplet("hi", new UInt32(12), new Short((short) 99)), new DBus.Binding.TestStruct("hi", new UInt32(12), new Short((short) 99))); + + Map in = new HashMap(); + Map> out = new HashMap>(); + test(DBus.Binding.Tests.class, tests, "InvertMapping", out, in); + + in.put("hi", "there"); + in.put("to", "there"); + in.put("from", "here"); + in.put("in", "out"); + List l = new Vector(); + l.add("hi"); + l.add("to"); + out.put("there", l); + l = new Vector(); + l.add("from"); + out.put("here", l); + l = new Vector(); + l.add("in"); + out.put("out", l); + test(DBus.Binding.Tests.class, tests, "InvertMapping", out, in); + + primitizeTest(tests, new Integer(1)); + primitizeTest(tests, + new Variant>>>( + new Variant>>( + new Variant>( + new Variant("Hi"))))); + primitizeTest(tests, new Variant>(in, new DBusMapType(String.class, String.class))); + + test(DBus.Binding.Tests.class, tests, "Trigger", null, "/Test", new UInt64(21389479283L)); + + try { + ctc.conn.sendSignal(new DBus.Binding.TestClient.Trigger("/Test", new UInt16(15), 12.5)); + } catch (DBusException DBe) { + if (Debug.debug) Debug.print(DBe); + throw new DBusExecutionException(DBe.getMessage()); + } + + try { + Thread.sleep(10000); + } catch (InterruptedException Ie) { + } + + test(DBus.Binding.Tests.class, tests, "Exit", null); + } + + public static void testArray(Class iface, Object proxy, String method, Class arrayType, Object content) { + Object array = Array.newInstance(arrayType, 0); + test(iface, proxy, method, array, array); + Random r = new Random(); + int l = (r.nextInt() % 100); + array = Array.newInstance(arrayType, (l < 0 ? -l : l) + 15); + if (null != content) + Arrays.fill((Object[]) array, content); + test(iface, proxy, method, array, array); + } + + public static void compareArray(String test, Object a, Object b) { + if (!a.getClass().equals(b.getClass())) { + fail(test, "Incorrect return type; expected " + a.getClass() + " got " + b.getClass()); + return; + } + boolean pass = false; + + if (a instanceof Object[]) + pass = Arrays.equals((Object[]) a, (Object[]) b); + else if (a instanceof byte[]) + pass = Arrays.equals((byte[]) a, (byte[]) b); + else if (a instanceof boolean[]) + pass = Arrays.equals((boolean[]) a, (boolean[]) b); + else if (a instanceof int[]) + pass = Arrays.equals((int[]) a, (int[]) b); + else if (a instanceof short[]) + pass = Arrays.equals((short[]) a, (short[]) b); + else if (a instanceof long[]) + pass = Arrays.equals((long[]) a, (long[]) b); + else if (a instanceof double[]) + pass = Arrays.equals((double[]) a, (double[]) b); + + if (pass) + pass(test); + else { + String s = "Incorrect return value; expected "; + s += collapseArray(a); + s += " got "; + s += collapseArray(b); + fail(test, s); + } + } + + public static void main(String[] args) { + try { + /* init */ + DBusConnection conn = DBusConnection.getConnection(DBusConnection.SESSION); + ctc = new cross_test_client(conn); + conn.exportObject("/Test", ctc); + conn.addSigHandler(DBus.Binding.TestSignals.Triggered.class, ctc); + DBus.Binding.Tests tests = conn.getRemoteObject("org.freedesktop.DBus.Binding.TestServer", "/Test", DBus.Binding.Tests.class); + DBus.Binding.SingleTests singletests = conn.getRemoteObject("org.freedesktop.DBus.Binding.TestServer", "/Test", DBus.Binding.SingleTests.class); + DBus.Peer peer = conn.getRemoteObject("org.freedesktop.DBus.Binding.TestServer", "/Test", DBus.Peer.class); + DBus.Introspectable intro = conn.getRemoteObject("org.freedesktop.DBus.Binding.TestServer", "/Test", DBus.Introspectable.class); + + DBus.Introspectable rootintro = conn.getRemoteObject("org.freedesktop.DBus.Binding.TestServer", "/", DBus.Introspectable.class); + + doTests(peer, intro, rootintro, tests, singletests); + + /* report results */ + for (String s : passed) + System.out.println(s + " pass"); + int i = 1; + for (String s : failed.keySet()) + for (String r : failed.get(s)) { + System.out.println(s + " fail " + i); + System.out.println("report " + i + ": " + r); + i++; + } + + conn.disconnect(); + } catch (DBusException DBe) { + DBe.printStackTrace(); + System.exit(1); + } + } +} diff --git a/federation/sssd/src/test/java/org/freedesktop/dbus/cross_test_server.java b/federation/sssd/src/test/java/org/freedesktop/dbus/cross_test_server.java new file mode 100644 index 0000000000..61bba4a6d5 --- /dev/null +++ b/federation/sssd/src/test/java/org/freedesktop/dbus/cross_test_server.java @@ -0,0 +1,342 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; + +import org.freedesktop.DBus; +import org.freedesktop.dbus.exceptions.DBusException; +import org.freedesktop.dbus.exceptions.DBusExecutionException; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; +import java.util.Vector; + +public class cross_test_server implements DBus.Binding.Tests, DBus.Binding.SingleTests, DBusSigHandler { + private DBusConnection conn; + boolean run = true; + private Set done = new TreeSet(); + private Set notdone = new TreeSet(); + + { + notdone.add("org.freedesktop.DBus.Binding.Tests.Identity"); + notdone.add("org.freedesktop.DBus.Binding.Tests.IdentityByte"); + notdone.add("org.freedesktop.DBus.Binding.Tests.IdentityBool"); + notdone.add("org.freedesktop.DBus.Binding.Tests.IdentityInt16"); + notdone.add("org.freedesktop.DBus.Binding.Tests.IdentityUInt16"); + notdone.add("org.freedesktop.DBus.Binding.Tests.IdentityInt32"); + notdone.add("org.freedesktop.DBus.Binding.Tests.IdentityUInt32"); + notdone.add("org.freedesktop.DBus.Binding.Tests.IdentityInt64"); + notdone.add("org.freedesktop.DBus.Binding.Tests.IdentityUInt64"); + notdone.add("org.freedesktop.DBus.Binding.Tests.IdentityDouble"); + notdone.add("org.freedesktop.DBus.Binding.Tests.IdentityString"); + notdone.add("org.freedesktop.DBus.Binding.Tests.IdentityArray"); + notdone.add("org.freedesktop.DBus.Binding.Tests.IdentityByteArray"); + notdone.add("org.freedesktop.DBus.Binding.Tests.IdentityBoolArray"); + notdone.add("org.freedesktop.DBus.Binding.Tests.IdentityInt16Array"); + notdone.add("org.freedesktop.DBus.Binding.Tests.IdentityUInt16Array"); + notdone.add("org.freedesktop.DBus.Binding.Tests.IdentityInt32Array"); + notdone.add("org.freedesktop.DBus.Binding.Tests.IdentityUInt32Array"); + notdone.add("org.freedesktop.DBus.Binding.Tests.IdentityInt64Array"); + notdone.add("org.freedesktop.DBus.Binding.Tests.IdentityUInt64Array"); + notdone.add("org.freedesktop.DBus.Binding.Tests.IdentityDoubleArray"); + notdone.add("org.freedesktop.DBus.Binding.Tests.IdentityStringArray"); + notdone.add("org.freedesktop.DBus.Binding.Tests.Sum"); + notdone.add("org.freedesktop.DBus.Binding.SingleTests.Sum"); + notdone.add("org.freedesktop.DBus.Binding.Tests.InvertMapping"); + notdone.add("org.freedesktop.DBus.Binding.Tests.DeStruct"); + notdone.add("org.freedesktop.DBus.Binding.Tests.Primitize"); + notdone.add("org.freedesktop.DBus.Binding.Tests.Invert"); + notdone.add("org.freedesktop.DBus.Binding.Tests.Trigger"); + notdone.add("org.freedesktop.DBus.Binding.Tests.Exit"); + notdone.add("org.freedesktop.DBus.Binding.TestClient.Trigger"); + } + + public cross_test_server(DBusConnection conn) { + this.conn = conn; + } + + public boolean isRemote() { + return false; + } + + @SuppressWarnings("unchecked") + @DBus.Description("Returns whatever it is passed") + public Variant Identity(Variant input) { + done.add("org.freedesktop.DBus.Binding.Tests.Identity"); + notdone.remove("org.freedesktop.DBus.Binding.Tests.Identity"); + return new Variant(input.getValue()); + } + + @DBus.Description("Returns whatever it is passed") + public byte IdentityByte(byte input) { + done.add("org.freedesktop.DBus.Binding.Tests.IdentityByte"); + notdone.remove("org.freedesktop.DBus.Binding.Tests.IdentityByte"); + return input; + } + + @DBus.Description("Returns whatever it is passed") + public boolean IdentityBool(boolean input) { + done.add("org.freedesktop.DBus.Binding.Tests.IdentityBool"); + notdone.remove("org.freedesktop.DBus.Binding.Tests.IdentityBool"); + return input; + } + + @DBus.Description("Returns whatever it is passed") + public short IdentityInt16(short input) { + done.add("org.freedesktop.DBus.Binding.Tests.IdentityInt16"); + notdone.remove("org.freedesktop.DBus.Binding.Tests.IdentityInt16"); + return input; + } + + @DBus.Description("Returns whatever it is passed") + public UInt16 IdentityUInt16(UInt16 input) { + done.add("org.freedesktop.DBus.Binding.Tests.IdentityUInt16"); + notdone.remove("org.freedesktop.DBus.Binding.Tests.IdentityUInt16"); + return input; + } + + @DBus.Description("Returns whatever it is passed") + public int IdentityInt32(int input) { + done.add("org.freedesktop.DBus.Binding.Tests.IdentityInt32"); + notdone.remove("org.freedesktop.DBus.Binding.Tests.IdentityInt32"); + return input; + } + + @DBus.Description("Returns whatever it is passed") + public UInt32 IdentityUInt32(UInt32 input) { + done.add("org.freedesktop.DBus.Binding.Tests.IdentityUInt32"); + notdone.remove("org.freedesktop.DBus.Binding.Tests.IdentityUInt32"); + return input; + } + + @DBus.Description("Returns whatever it is passed") + public long IdentityInt64(long input) { + done.add("org.freedesktop.DBus.Binding.Tests.IdentityInt64"); + notdone.remove("org.freedesktop.DBus.Binding.Tests.IdentityInt64"); + return input; + } + + @DBus.Description("Returns whatever it is passed") + public UInt64 IdentityUInt64(UInt64 input) { + done.add("org.freedesktop.DBus.Binding.Tests.IdentityUInt64"); + notdone.remove("org.freedesktop.DBus.Binding.Tests.IdentityUInt64"); + return input; + } + + @DBus.Description("Returns whatever it is passed") + public double IdentityDouble(double input) { + done.add("org.freedesktop.DBus.Binding.Tests.IdentityDouble"); + notdone.remove("org.freedesktop.DBus.Binding.Tests.IdentityDouble"); + return input; + } + + @DBus.Description("Returns whatever it is passed") + public String IdentityString(String input) { + done.add("org.freedesktop.DBus.Binding.Tests.IdentityString"); + notdone.remove("org.freedesktop.DBus.Binding.Tests.IdentityString"); + return input; + } + + @DBus.Description("Returns whatever it is passed") + public Variant[] IdentityArray(Variant[] input) { + done.add("org.freedesktop.DBus.Binding.Tests.IdentityArray"); + notdone.remove("org.freedesktop.DBus.Binding.Tests.IdentityArray"); + return input; + } + + @DBus.Description("Returns whatever it is passed") + public byte[] IdentityByteArray(byte[] input) { + done.add("org.freedesktop.DBus.Binding.Tests.IdentityByteArray"); + notdone.remove("org.freedesktop.DBus.Binding.Tests.IdentityByteArray"); + return input; + } + + @DBus.Description("Returns whatever it is passed") + public boolean[] IdentityBoolArray(boolean[] input) { + done.add("org.freedesktop.DBus.Binding.Tests.IdentityBoolArray"); + notdone.remove("org.freedesktop.DBus.Binding.Tests.IdentityBoolArray"); + return input; + } + + @DBus.Description("Returns whatever it is passed") + public short[] IdentityInt16Array(short[] input) { + done.add("org.freedesktop.DBus.Binding.Tests.IdentityInt16Array"); + notdone.remove("org.freedesktop.DBus.Binding.Tests.IdentityInt16Array"); + return input; + } + + @DBus.Description("Returns whatever it is passed") + public UInt16[] IdentityUInt16Array(UInt16[] input) { + done.add("org.freedesktop.DBus.Binding.Tests.IdentityUInt16Array"); + notdone.remove("org.freedesktop.DBus.Binding.Tests.IdentityUInt16Array"); + return input; + } + + @DBus.Description("Returns whatever it is passed") + public int[] IdentityInt32Array(int[] input) { + done.add("org.freedesktop.DBus.Binding.Tests.IdentityInt32Array"); + notdone.remove("org.freedesktop.DBus.Binding.Tests.IdentityInt32Array"); + return input; + } + + @DBus.Description("Returns whatever it is passed") + public UInt32[] IdentityUInt32Array(UInt32[] input) { + done.add("org.freedesktop.DBus.Binding.Tests.IdentityUInt32Array"); + notdone.remove("org.freedesktop.DBus.Binding.Tests.IdentityUInt32Array"); + return input; + } + + @DBus.Description("Returns whatever it is passed") + public long[] IdentityInt64Array(long[] input) { + done.add("org.freedesktop.DBus.Binding.Tests.IdentityInt64Array"); + notdone.remove("org.freedesktop.DBus.Binding.Tests.IdentityInt64Array"); + return input; + } + + @DBus.Description("Returns whatever it is passed") + public UInt64[] IdentityUInt64Array(UInt64[] input) { + done.add("org.freedesktop.DBus.Binding.Tests.IdentityUInt64Array"); + notdone.remove("org.freedesktop.DBus.Binding.Tests.IdentityUInt64Array"); + return input; + } + + @DBus.Description("Returns whatever it is passed") + public double[] IdentityDoubleArray(double[] input) { + done.add("org.freedesktop.DBus.Binding.Tests.IdentityDoubleArray"); + notdone.remove("org.freedesktop.DBus.Binding.Tests.IdentityDoubleArray"); + return input; + } + + @DBus.Description("Returns whatever it is passed") + public String[] IdentityStringArray(String[] input) { + done.add("org.freedesktop.DBus.Binding.Tests.IdentityStringArray"); + notdone.remove("org.freedesktop.DBus.Binding.Tests.IdentityStringArray"); + return input; + } + + @DBus.Description("Returns the sum of the values in the input list") + public long Sum(int[] a) { + done.add("org.freedesktop.DBus.Binding.Tests.Sum"); + notdone.remove("org.freedesktop.DBus.Binding.Tests.Sum"); + long sum = 0; + for (int b : a) sum += b; + return sum; + } + + @DBus.Description("Returns the sum of the values in the input list") + public UInt32 Sum(byte[] a) { + done.add("org.freedesktop.DBus.Binding.SingleTests.Sum"); + notdone.remove("org.freedesktop.DBus.Binding.SingleTests.Sum"); + int sum = 0; + for (byte b : a) sum += (b < 0 ? b + 256 : b); + return new UInt32(sum % (UInt32.MAX_VALUE + 1)); + } + + @DBus.Description("Given a map of A => B, should return a map of B => a list of all the As which mapped to B") + public Map> InvertMapping(Map a) { + done.add("org.freedesktop.DBus.Binding.Tests.InvertMapping"); + notdone.remove("org.freedesktop.DBus.Binding.Tests.InvertMapping"); + HashMap> m = new HashMap>(); + for (String s : a.keySet()) { + String b = a.get(s); + List l = m.get(b); + if (null == l) { + l = new Vector(); + m.put(b, l); + } + l.add(s); + } + return m; + } + + @DBus.Description("This method returns the contents of a struct as separate values") + public DBus.Binding.Triplet DeStruct(DBus.Binding.TestStruct a) { + done.add("org.freedesktop.DBus.Binding.Tests.DeStruct"); + notdone.remove("org.freedesktop.DBus.Binding.Tests.DeStruct"); + return new DBus.Binding.Triplet(a.a, a.b, a.c); + } + + @DBus.Description("Given any compound type as a variant, return all the primitive types recursively contained within as an array of variants") + @SuppressWarnings("unchecked") + public List> Primitize(Variant a) { + done.add("org.freedesktop.DBus.Binding.Tests.Primitize"); + notdone.remove("org.freedesktop.DBus.Binding.Tests.Primitize"); + return cross_test_client.PrimitizeRecurse(a.getValue(), a.getType()); + } + + @DBus.Description("inverts it's input") + public boolean Invert(boolean a) { + done.add("org.freedesktop.DBus.Binding.Tests.Invert"); + notdone.remove("org.freedesktop.DBus.Binding.Tests.Invert"); + return !a; + } + + @DBus.Description("triggers sending of a signal from the supplied object with the given parameter") + public void Trigger(String a, UInt64 b) { + done.add("org.freedesktop.DBus.Binding.Tests.Trigger"); + notdone.remove("org.freedesktop.DBus.Binding.Tests.Trigger"); + try { + conn.sendSignal(new DBus.Binding.TestSignals.Triggered(a, b)); + } catch (DBusException DBe) { + throw new DBusExecutionException(DBe.getMessage()); + } + } + + public void Exit() { + done.add("org.freedesktop.DBus.Binding.Tests.Exit"); + notdone.remove("org.freedesktop.DBus.Binding.Tests.Exit"); + run = false; + synchronized (this) { + notifyAll(); + } + } + + public void handle(DBus.Binding.TestClient.Trigger t) { + done.add("org.freedesktop.DBus.Binding.TestClient.Trigger"); + notdone.remove("org.freedesktop.DBus.Binding.TestClient.Trigger"); + try { + DBus.Binding.TestClient cb = conn.getRemoteObject(t.getSource(), "/Test", DBus.Binding.TestClient.class); + cb.Response(t.a, t.b); + } catch (DBusException DBe) { + throw new DBusExecutionException(DBe.getMessage()); + } + } + + public static void main(String[] args) { + try { + DBusConnection conn = DBusConnection.getConnection(DBusConnection.SESSION); + conn.requestBusName("org.freedesktop.DBus.Binding.TestServer"); + cross_test_server cts = new cross_test_server(conn); + conn.addSigHandler(DBus.Binding.TestClient.Trigger.class, cts); + conn.exportObject("/Test", cts); + synchronized (cts) { + while (cts.run) { + try { + cts.wait(); + } catch (InterruptedException Ie) { + } + } + } + for (String s : cts.done) + System.out.println(s + " ok"); + for (String s : cts.notdone) + System.out.println(s + " untested"); + conn.disconnect(); + System.exit(0); + } catch (DBusException DBe) { + DBe.printStackTrace(); + System.exit(1); + } + } +} + diff --git a/federation/sssd/src/test/java/org/freedesktop/dbus/profile.java b/federation/sssd/src/test/java/org/freedesktop/dbus/profile.java new file mode 100644 index 0000000000..e6866a7041 --- /dev/null +++ b/federation/sssd/src/test/java/org/freedesktop/dbus/profile.java @@ -0,0 +1,378 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; + +import org.freedesktop.DBus.Introspectable; +import org.freedesktop.DBus.Peer; + +import java.util.HashMap; +import java.util.Random; +import java.util.Vector; + +class ProfileHandler implements DBusSigHandler { + public int c = 0; + + public void handle(Profiler.ProfileSignal s) { + if (0 == (c++ % profile.SIGNAL_INNER)) System.out.print("-"); + } +} + +/** + * Profiling tests. + */ +public class profile { + public static final int SIGNAL_INNER = 100; + public static final int SIGNAL_OUTER = 100; + public static final int PING_INNER = 100; + public static final int PING_OUTER = 100; + public static final int BYTES = 2000000; + public static final int INTROSPECTION_OUTER = 100; + public static final int INTROSPECTION_INNER = 10; + public static final int STRUCT_OUTER = 100; + public static final int STRUCT_INNER = 10; + public static final int LIST_OUTER = 100; + public static final int LIST_INNER = 10; + public static final int LIST_LENGTH = 100; + public static final int MAP_OUTER = 100; + public static final int MAP_INNER = 10; + public static final int MAP_LENGTH = 100; + public static final int ARRAY_OUTER = 100; + public static final int ARRAY_INNER = 10; + public static final int ARRAY_LENGTH = 1000; + public static final int STRING_ARRAY_OUTER = 10; + public static final int STRING_ARRAY_INNER = 1; + public static final int STRING_ARRAY_LENGTH = 20000; + + public static class Log { + private long last; + private int[] deltas; + private int current = 0; + + public Log(int size) { + deltas = new int[size]; + } + + public void start() { + last = System.currentTimeMillis(); + } + + public void stop() { + deltas[current] = (int) (System.currentTimeMillis() - last); + current++; + } + + public double mean() { + if (0 == current) return 0; + long sum = 0; + for (int i = 0; i < current; i++) + sum += deltas[i]; + return sum /= current; + } + + public long min() { + int m = Integer.MAX_VALUE; + for (int i = 0; i < current; i++) + if (deltas[i] < m) m = deltas[i]; + return m; + } + + public long max() { + int m = 0; + for (int i = 0; i < current; i++) + if (deltas[i] > m) m = deltas[i]; + return m; + } + + public double stddev() { + double mean = mean(); + double sum = 0; + for (int i = 0; i < current; i++) + sum += (deltas[i] - mean) * (deltas[i] - mean); + return Math.sqrt(sum / (current - 1)); + } + } + + public static void main(String[] args) { + try { + if (0 == args.length) { + System.out.println("You must specify a profile type."); + System.out.println("Syntax: profile "); + System.exit(1); + } + DBusConnection conn = DBusConnection.getConnection(DBusConnection.SESSION); + conn.requestBusName("org.freedesktop.DBus.java.profiler"); + if ("pings".equals(args[0])) { + int count = PING_INNER * PING_OUTER; + System.out.print("Sending " + count + " pings..."); + Peer p = conn.getRemoteObject("org.freedesktop.DBus.java.profiler", "/Profiler", Peer.class); + Log l = new Log(count); + long t = System.currentTimeMillis(); + for (int i = 0; i < PING_OUTER; i++) { + for (int j = 0; j < PING_INNER; j++) { + l.start(); + p.Ping(); + l.stop(); + } + System.out.print(""); + } + t = System.currentTimeMillis() - t; + System.out.println(" done."); + System.out.println("min/max/avg (ms): " + l.min() + "/" + l.max() + "/" + l.mean()); + System.out.println("deviation: " + l.stddev()); + System.out.println("Total time: " + t + "ms"); + } else if ("strings".equals(args[0])) { + int count = STRING_ARRAY_INNER * STRING_ARRAY_OUTER; + System.out.print("Sending array of " + STRING_ARRAY_LENGTH + " strings " + count + " times."); + ProfilerInstance pi = new ProfilerInstance(); + conn.exportObject("/Profiler", pi); + Profiler p = conn.getRemoteObject("org.freedesktop.DBus.java.profiler", "/Profiler", Profiler.class); + String[] v = new String[STRING_ARRAY_LENGTH]; + Random r = new Random(); + for (int i = 0; i < STRING_ARRAY_LENGTH; i++) v[i] = "" + r.nextInt(); + Log l = new Log(count); + long t = System.currentTimeMillis(); + for (int i = 0; i < STRING_ARRAY_OUTER; i++) { + for (int j = 0; j < STRING_ARRAY_INNER; j++) { + l.start(); + p.stringarray(v); + l.stop(); + } + System.out.print(""); + } + t = System.currentTimeMillis() - t; + System.out.println(" done."); + System.out.println("min/max/avg (ms): " + l.min() + "/" + l.max() + "/" + l.mean()); + System.out.println("deviation: " + l.stddev()); + System.out.println("Total time: " + t + "ms"); + } else if ("arrays".equals(args[0])) { + int count = ARRAY_INNER * ARRAY_OUTER; + System.out.print("Sending array of " + ARRAY_LENGTH + " ints " + count + " times."); + ProfilerInstance pi = new ProfilerInstance(); + conn.exportObject("/Profiler", pi); + Profiler p = conn.getRemoteObject("org.freedesktop.DBus.java.profiler", "/Profiler", Profiler.class); + int[] v = new int[ARRAY_LENGTH]; + Random r = new Random(); + for (int i = 0; i < ARRAY_LENGTH; i++) v[i] = r.nextInt(); + Log l = new Log(count); + long t = System.currentTimeMillis(); + for (int i = 0; i < ARRAY_OUTER; i++) { + for (int j = 0; j < ARRAY_INNER; j++) { + l.start(); + p.array(v); + l.stop(); + } + System.out.print(""); + } + t = System.currentTimeMillis() - t; + System.out.println(" done."); + System.out.println("min/max/avg (ms): " + l.min() + "/" + l.max() + "/" + l.mean()); + System.out.println("deviation: " + l.stddev()); + System.out.println("Total time: " + t + "ms"); + } else if ("maps".equals(args[0])) { + int count = MAP_INNER * MAP_OUTER; + System.out.print("Sending map of " + MAP_LENGTH + " string=>strings " + count + " times."); + ProfilerInstance pi = new ProfilerInstance(); + conn.exportObject("/Profiler", pi); + Profiler p = conn.getRemoteObject("org.freedesktop.DBus.java.profiler", "/Profiler", Profiler.class); + HashMap m = new HashMap(); + for (int i = 0; i < MAP_LENGTH; i++) + m.put("" + i, "hello"); + Log l = new Log(count); + long t = System.currentTimeMillis(); + for (int i = 0; i < MAP_OUTER; i++) { + for (int j = 0; j < MAP_INNER; j++) { + l.start(); + p.map(m); + l.stop(); + } + System.out.print(""); + } + t = System.currentTimeMillis() - t; + System.out.println(" done."); + System.out.println("min/max/avg (ms): " + l.min() + "/" + l.max() + "/" + l.mean()); + System.out.println("deviation: " + l.stddev()); + System.out.println("Total time: " + t + "ms"); + } else if ("lists".equals(args[0])) { + int count = LIST_OUTER * LIST_INNER; + System.out.print("Sending list of " + LIST_LENGTH + " strings " + count + " times."); + ProfilerInstance pi = new ProfilerInstance(); + conn.exportObject("/Profiler", pi); + Profiler p = conn.getRemoteObject("org.freedesktop.DBus.java.profiler", "/Profiler", Profiler.class); + Vector v = new Vector(); + for (int i = 0; i < LIST_LENGTH; i++) + v.add("hello " + i); + Log l = new Log(count); + long t = System.currentTimeMillis(); + for (int i = 0; i < LIST_OUTER; i++) { + for (int j = 0; j < LIST_INNER; j++) { + l.start(); + p.list(v); + l.stop(); + } + System.out.print(""); + } + t = System.currentTimeMillis() - t; + System.out.println(" done."); + System.out.println("min/max/avg (ms): " + l.min() + "/" + l.max() + "/" + l.mean()); + System.out.println("deviation: " + l.stddev()); + System.out.println("Total time: " + t + "ms"); + } else if ("structs".equals(args[0])) { + int count = STRUCT_OUTER * STRUCT_INNER; + System.out.print("Sending a struct " + count + " times."); + ProfilerInstance pi = new ProfilerInstance(); + conn.exportObject("/Profiler", pi); + Profiler p = conn.getRemoteObject("org.freedesktop.DBus.java.profiler", "/Profiler", Profiler.class); + ProfileStruct ps = new ProfileStruct("hello", new UInt32(18), 500L); + Log l = new Log(count); + long t = System.currentTimeMillis(); + for (int i = 0; i < STRUCT_OUTER; i++) { + for (int j = 0; j < STRUCT_INNER; j++) { + l.start(); + p.struct(ps); + l.stop(); + } + System.out.print(""); + } + t = System.currentTimeMillis() - t; + System.out.println(" done."); + System.out.println("min/max/avg (ms): " + l.min() + "/" + l.max() + "/" + l.mean()); + System.out.println("deviation: " + l.stddev()); + System.out.println("Total time: " + t + "ms"); + } else if ("introspect".equals(args[0])) { + int count = INTROSPECTION_OUTER * INTROSPECTION_INNER; + System.out.print("Recieving introspection data " + count + " times."); + ProfilerInstance pi = new ProfilerInstance(); + conn.exportObject("/Profiler", pi); + Introspectable is = conn.getRemoteObject("org.freedesktop.DBus.java.profiler", "/Profiler", Introspectable.class); + Log l = new Log(count); + long t = System.currentTimeMillis(); + String s = null; + for (int i = 0; i < INTROSPECTION_OUTER; i++) { + for (int j = 0; j < INTROSPECTION_INNER; j++) { + l.start(); + s = is.Introspect(); + l.stop(); + } + System.out.print(""); + } + t = System.currentTimeMillis() - t; + System.out.println(" done."); + System.out.println("min/max/avg (ms): " + l.min() + "/" + l.max() + "/" + l.mean()); + System.out.println("deviation: " + l.stddev()); + System.out.println("Total time: " + t + "ms"); + System.out.println("Introspect data: " + s); + } else if ("bytes".equals(args[0])) { + System.out.print("Sending " + BYTES + " bytes"); + ProfilerInstance pi = new ProfilerInstance(); + conn.exportObject("/Profiler", pi); + Profiler p = conn.getRemoteObject("org.freedesktop.DBus.java.profiler", "/Profiler", Profiler.class); + byte[] bs = new byte[BYTES]; + for (int i = 0; i < BYTES; i++) + bs[i] = (byte) i; + long t = System.currentTimeMillis(); + p.bytes(bs); + System.out.println(" done in " + (System.currentTimeMillis() - t) + "ms."); + } else if ("rate".equals(args[0])) { + ProfilerInstance pi = new ProfilerInstance(); + conn.exportObject("/Profiler", pi); + Profiler p = conn.getRemoteObject("org.freedesktop.DBus.java.profiler", "/Profiler", Profiler.class); + Peer peer = conn.getRemoteObject("org.freedesktop.DBus.java.profiler", "/Profiler", Peer.class); + conn.changeThreadCount((byte) 1); + + long start = System.currentTimeMillis(); + int count = 0; + do { + p.Pong(); + count++; + } while (count < 10000); + long end = System.currentTimeMillis(); + System.out.println("No payload: " + ((count * 1000) / (end - start)) + " RT/second"); + start = System.currentTimeMillis(); + count = 0; + do { + p.Pong(); + count++; + } while (count < 10000); + peer.Ping(); + end = System.currentTimeMillis(); + System.out.println("No payload, One way: " + ((count * 1000) / (end - start)) + " /second"); + int len = 256; + while (len <= 32768) { + byte[] bs = new byte[len]; + count = 0; + start = System.currentTimeMillis(); + do { + p.bytes(bs); + count++; + } while (count < 1000); + end = System.currentTimeMillis(); + long ms = end - start; + double cps = (count * 1000) / ms; + double rate = (len * cps) / (1024.0 * 1024.0); + System.out.println(len + " byte array) " + (count * len) + " bytes in " + ms + "ms (in " + count + " calls / " + (int) cps + " CPS): " + rate + "MB/s"); + len <<= 1; + } + len = 256; + while (len <= 32768) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < len; i++) sb.append('a'); + String s = sb.toString(); + end = System.currentTimeMillis() + 500; + count = 0; + do { + p.string(s); + count++; + } while (count < 1000); + long ms = end - start; + double cps = (count * 1000) / ms; + double rate = (len * cps) / (1024.0 * 1024.0); + System.out.println(len + " string) " + (count * len) + " bytes in " + ms + "ms (in " + count + " calls / " + (int) cps + " CPS): " + rate + "MB/s"); + len <<= 1; + } + } else if ("signals".equals(args[0])) { + int count = SIGNAL_OUTER * SIGNAL_INNER; + System.out.print("Sending " + count + " signals"); + ProfileHandler ph = new ProfileHandler(); + conn.addSigHandler(Profiler.ProfileSignal.class, ph); + Log l = new Log(count); + Profiler.ProfileSignal ps = new Profiler.ProfileSignal("/"); + long t = System.currentTimeMillis(); + for (int i = 0; i < SIGNAL_OUTER; i++) { + for (int j = 0; j < SIGNAL_INNER; j++) { + l.start(); + conn.sendSignal(ps); + l.stop(); + } + System.out.print(""); + } + t = System.currentTimeMillis() - t; + System.out.println(" done."); + System.out.println("min/max/avg (ms): " + l.min() + "/" + l.max() + "/" + l.mean()); + System.out.println("deviation: " + l.stddev()); + System.out.println("Total time: " + t + "ms"); + while (ph.c < count) try { + Thread.sleep(100); + } catch (InterruptedException Ie) { + } + ; + } else { + conn.disconnect(); + System.out.println("Invalid profile ``" + args[0] + "''."); + System.out.println("Syntax: profile "); + System.exit(1); + } + conn.disconnect(); + } catch (Exception e) { + e.printStackTrace(); + System.exit(1); + } + } +} diff --git a/federation/sssd/src/test/java/org/freedesktop/dbus/test.java b/federation/sssd/src/test/java/org/freedesktop/dbus/test.java new file mode 100644 index 0000000000..af6a736623 --- /dev/null +++ b/federation/sssd/src/test/java/org/freedesktop/dbus/test.java @@ -0,0 +1,991 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; + +import org.freedesktop.DBus; +import org.freedesktop.DBus.Error.MatchRuleInvalid; +import org.freedesktop.DBus.Error.ServiceUnknown; +import org.freedesktop.DBus.Error.UnknownObject; +import org.freedesktop.DBus.Introspectable; +import org.freedesktop.DBus.Peer; +import org.freedesktop.DBus.Properties; +import org.freedesktop.dbus.exceptions.DBusException; +import org.freedesktop.dbus.exceptions.DBusExecutionException; +import org.freedesktop.dbus.exceptions.NotConnected; + +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.text.Collator; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.Vector; + +class testnewclass implements TestNewInterface { + public boolean isRemote() { + return false; + } + + public String getName() { + return toString(); + } +} + +class testclass implements TestRemoteInterface, TestRemoteInterface2, TestSignalInterface, TestSignalInterface2, Properties { + private DBusConnection conn; + + public testclass(DBusConnection conn) { + this.conn = conn; + } + + public String Introspect() { + return "Not XML"; + } + + public int[][] teststructstruct(TestStruct3 in) { + List> lli = in.b; + int[][] out = new int[lli.size()][]; + for (int j = 0; j < out.length; j++) { + out[j] = new int[lli.get(j).size()]; + for (int k = 0; k < out[j].length; k++) + out[j][k] = lli.get(j).get(k); + } + return out; + } + + public float testfloat(float[] f) { + if (f.length < 4 || + f[0] != 17.093f || + f[1] != -23f || + f[2] != 0.0f || + f[3] != 31.42f) + test.fail("testfloat got incorrect array"); + return f[0]; + } + + public void newpathtest(Path p) { + if (!p.toString().equals("/new/path/test")) + test.fail("new path test got wrong path"); + } + + public void waitawhile() { + System.out.println("Sleeping."); + try { + Thread.sleep(1000); + } catch (InterruptedException Ie) { + } + System.out.println("Done sleeping."); + } + + public TestTuple, Boolean> show(A in) { + System.out.println("Showing Stuff: " + in.getClass() + "(" + in + ")"); + if (!(in instanceof Integer) || ((Integer) in).intValue() != 234) + test.fail("show received the wrong arguments"); + DBusCallInfo info = DBusConnection.getCallInfo(); + List l = new Vector(); + l.add(1953); + return new TestTuple, Boolean>(info.getSource(), l, true); + } + + @SuppressWarnings("unchecked") + public T dostuff(TestStruct foo) { + System.out.println("Doing Stuff " + foo); + System.out.println(" -- (" + foo.a.getClass() + ", " + foo.b.getClass() + ", " + foo.c.getClass() + ")"); + if (!(foo instanceof TestStruct) || + !(foo.a instanceof String) || + !(foo.b instanceof UInt32) || + !(foo.c instanceof Variant) || + !"bar".equals(foo.a) || + foo.b.intValue() != 52 || + !(foo.c.getValue() instanceof Boolean) || + ((Boolean) foo.c.getValue()).booleanValue() != true) + test.fail("dostuff received the wrong arguments"); + return (T) foo.c.getValue(); + } + + /** + * Local classes MUST implement this to return false + */ + public boolean isRemote() { + return false; + } + + /** + * The method we are exporting to the Bus. + */ + public List sampleArray(List ss, Integer[] is, long[] ls) { + System.out.println("Got an array:"); + for (String s : ss) + System.out.println("--" + s); + if (ss.size() != 5 || + !"hi".equals(ss.get(0)) || + !"hello".equals(ss.get(1)) || + !"hej".equals(ss.get(2)) || + !"hey".equals(ss.get(3)) || + !"aloha".equals(ss.get(4))) + test.fail("sampleArray, String array contents incorrect"); + System.out.println("Got an array:"); + for (Integer i : is) + System.out.println("--" + i); + if (is.length != 4 || + is[0].intValue() != 1 || + is[1].intValue() != 5 || + is[2].intValue() != 7 || + is[3].intValue() != 9) + test.fail("sampleArray, Integer array contents incorrect"); + System.out.println("Got an array:"); + for (long l : ls) + System.out.println("--" + l); + if (ls.length != 4 || + ls[0] != 2 || + ls[1] != 6 || + ls[2] != 8 || + ls[3] != 12) + test.fail("sampleArray, Integer array contents incorrect"); + Vector v = new Vector(); + v.add(-1); + v.add(-5); + v.add(-7); + v.add(-12); + v.add(-18); + return v; + } + + public String getName() { + return "This Is A UTF-8 Name: س !!"; + } + + public String getNameAndThrow() throws TestException { + throw new TestException("test"); + } + + public boolean check() { + System.out.println("Being checked"); + return false; + } + + public int frobnicate(List n, Map> m, T v) { + if (null == n) + test.fail("List was null"); + if (n.size() != 3) + test.fail("List was wrong size (expected 3, actual " + n.size() + ")"); + if (n.get(0) != 2L || + n.get(1) != 5L || + n.get(2) != 71L) + test.fail("List has wrong contents"); + if (!(v instanceof Integer)) + test.fail("v not an Integer"); + if (((Integer) v) != 13) + test.fail("v is incorrect"); + if (null == m) + test.fail("Map was null"); + if (m.size() != 1) + test.fail("Map was wrong size"); + if (!m.keySet().contains("stuff")) + test.fail("Incorrect key"); + Map mus = m.get("stuff"); + if (null == mus) + test.fail("Sub-Map was null"); + if (mus.size() != 3) + test.fail("Sub-Map was wrong size"); + if (!(new Short((short) 5).equals(mus.get(new UInt16(4))))) + test.fail("Sub-Map has wrong contents"); + if (!(new Short((short) 6).equals(mus.get(new UInt16(5))))) + test.fail("Sub-Map has wrong contents"); + if (!(new Short((short) 7).equals(mus.get(new UInt16(6))))) + test.fail("Sub-Map has wrong contents"); + return -5; + } + + public DBusInterface getThis(DBusInterface t) { + if (!t.equals(this)) + test.fail("Didn't get this properly"); + return this; + } + + public void throwme() throws TestException { + throw new TestException("test"); + } + + public TestSerializable testSerializable(byte b, TestSerializable s, int i) { + System.out.println("Recieving TestSerializable: " + s); + if (b != 12 + || i != 13 + || !(s.getInt() == 1) + || !(s.getString().equals("woo")) + || !(s.getVector().size() == 3) + || !(s.getVector().get(0) == 1) + || !(s.getVector().get(1) == 2) + || !(s.getVector().get(2) == 3)) + test.fail("Error in recieving custom synchronisation"); + return s; + } + + public String recursionTest() { + try { + TestRemoteInterface tri = conn.getRemoteObject("foo.bar.Test", "/Test", TestRemoteInterface.class); + return tri.getName(); + } catch (DBusException DBe) { + test.fail("Failed with error: " + DBe); + return ""; + } + } + + public int overload(String s) { + return 1; + } + + public int overload(byte b) { + return 2; + } + + public int overload() { + DBusCallInfo info = DBusConnection.getCallInfo(); + if ("org.freedesktop.dbus.test.AlternateTestInterface".equals(info.getInterface())) + return 3; + else if ("org.freedesktop.dbus.test.TestRemoteInterface".equals(info.getInterface())) + return 4; + else + return -1; + } + + public List> checklist(List> lli) { + return lli; + } + + public TestNewInterface getNew() { + testnewclass n = new testnewclass(); + try { + conn.exportObject("/new", n); + } catch (DBusException DBe) { + throw new DBusExecutionException(DBe.getMessage()); + } + return n; + } + + public void sig(Type[] s) { + if (s.length != 2 + || !s[0].equals(Byte.class) + || !(s[1] instanceof ParameterizedType) + || !Map.class.equals(((ParameterizedType) s[1]).getRawType()) + || ((ParameterizedType) s[1]).getActualTypeArguments().length != 2 + || !String.class.equals(((ParameterizedType) s[1]).getActualTypeArguments()[0]) + || !Integer.class.equals(((ParameterizedType) s[1]).getActualTypeArguments()[1])) + test.fail("Didn't send types correctly"); + } + + @SuppressWarnings("unchecked") + public void complexv(Variant v) { + if (!"a{ss}".equals(v.getSig()) + || !(v.getValue() instanceof Map) + || ((Map) v.getValue()).size() != 1 + || !"moo".equals(((Map) v.getValue()).get("cow"))) + test.fail("Didn't send variant correctly"); + } + + public void reg13291(byte[] as, byte[] bs) { + if (as.length != bs.length) test.fail("didn't receive identical byte arrays"); + for (int i = 0; i < as.length; i++) + if (as[i] != bs[i]) test.fail("didn't receive identical byte arrays"); + } + + @SuppressWarnings("unchecked") + public A Get(String interface_name, String property_name) { + return (A) new Path("/nonexistant/path"); + } + + public void Set(String interface_name, String property_name, A value) { + } + + public Map GetAll(String interface_name) { + return new HashMap(); + } + + public Path pathrv(Path a) { + return a; + } + + public List pathlistrv(List a) { + return a; + } + + public Map pathmaprv(Map a) { + return a; + } + + @SuppressWarnings("unchecked") + public Map svm() { + HashMap properties = new HashMap(); + HashMap> parameters = new HashMap>(); + + parameters.put("Name", new Variant("Joe")); + parameters.put("Password", new Variant("abcdef")); + + properties.put("Parameters", new Variant(parameters, "a{sv}")); + return (Map) properties; + } +} + +/** + * Typed signal handler for renamed signal + */ +class renamedsignalhandler implements DBusSigHandler { + /** + * Handling a signal + */ + public void handle(TestSignalInterface2.TestRenamedSignal t) { + if (false == test.done5) { + test.done5 = true; + } else { + test.fail("SignalHandler R has been run too many times"); + } + System.out.println("SignalHandler R Running"); + System.out.println("string(" + t.value + ") int(" + t.number + ")"); + if (!"Bar".equals(t.value) || !(new UInt32(42)).equals(t.number)) + test.fail("Incorrect TestRenamedSignal parameters"); + } +} + +/** + * Empty signal handler + */ +class emptysignalhandler implements DBusSigHandler { + /** + * Handling a signal + */ + public void handle(TestSignalInterface.EmptySignal t) { + if (false == test.done7) { + test.done7 = true; + } else { + test.fail("SignalHandler E has been run too many times"); + } + System.out.println("SignalHandler E Running"); + } +} + +/** + * Disconnect handler + */ +class disconnecthandler implements DBusSigHandler { + private DBusConnection conn; + private renamedsignalhandler sh; + + public disconnecthandler(DBusConnection conn, renamedsignalhandler sh) { + this.conn = conn; + this.sh = sh; + } + + /** + * Handling a signal + */ + public void handle(DBus.Local.Disconnected t) { + if (false == test.done6) { + test.done6 = true; + System.out.println("Handling disconnect, unregistering handler"); + try { + conn.removeSigHandler(TestSignalInterface2.TestRenamedSignal.class, sh); + } catch (DBusException DBe) { + DBe.printStackTrace(); + test.fail("Disconnect handler threw an exception: " + DBe); + } + } + } +} + + +/** + * Typed signal handler + */ +class pathsignalhandler implements DBusSigHandler { + /** + * Handling a signal + */ + public void handle(TestSignalInterface.TestPathSignal t) { + System.out.println("Path sighandler: " + t); + } +} + +/** + * Typed signal handler + */ +class signalhandler implements DBusSigHandler { + /** + * Handling a signal + */ + public void handle(TestSignalInterface.TestSignal t) { + if (false == test.done1) { + test.done1 = true; + } else { + test.fail("SignalHandler 1 has been run too many times"); + } + System.out.println("SignalHandler 1 Running"); + System.out.println("string(" + t.value + ") int(" + t.number + ")"); + if (!"Bar".equals(t.value) || !(new UInt32(42)).equals(t.number)) + test.fail("Incorrect TestSignal parameters"); + } +} + +/** + * Untyped signal handler + */ +class arraysignalhandler implements DBusSigHandler { + /** + * Handling a signal + */ + public void handle(TestSignalInterface.TestArraySignal t) { + try { + if (false == test.done2) { + test.done2 = true; + } else { + test.fail("SignalHandler 2 has been run too many times"); + } + System.out.println("SignalHandler 2 Running"); + if (t.v.size() != 1) + test.fail("Incorrect TestArraySignal array length: should be 1, actually " + t.v.size()); + System.out.println("Got a test array signal with Parameters: "); + for (String str : t.v.get(0).a) + System.out.println("--" + str); + System.out.println(t.v.get(0).b.getType()); + System.out.println(t.v.get(0).b.getValue()); + if (!(t.v.get(0).b.getValue() instanceof UInt64) || + 567L != ((UInt64) t.v.get(0).b.getValue()).longValue() || + t.v.get(0).a.size() != 5 || + !"hi".equals(t.v.get(0).a.get(0)) || + !"hello".equals(t.v.get(0).a.get(1)) || + !"hej".equals(t.v.get(0).a.get(2)) || + !"hey".equals(t.v.get(0).a.get(3)) || + !"aloha".equals(t.v.get(0).a.get(4))) + test.fail("Incorrect TestArraySignal parameters"); + + if (t.m.keySet().size() != 2) + test.fail("Incorrect TestArraySignal map size: should be 2, actually " + t.m.keySet().size()); + if (!(t.m.get(new UInt32(1)).b.getValue() instanceof UInt64) || + 678L != ((UInt64) t.m.get(new UInt32(1)).b.getValue()).longValue() || + !(t.m.get(new UInt32(42)).b.getValue() instanceof UInt64) || + 789L != ((UInt64) t.m.get(new UInt32(42)).b.getValue()).longValue()) + test.fail("Incorrect TestArraySignal parameters"); + + } catch (Exception e) { + e.printStackTrace(); + test.fail("SignalHandler 2 threw an exception: " + e); + } + } +} + +/** + * Object path signal handler + */ +class objectsignalhandler implements DBusSigHandler { + public void handle(TestSignalInterface.TestObjectSignal s) { + if (false == test.done3) { + test.done3 = true; + } else { + test.fail("SignalHandler 3 has been run too many times"); + } + System.out.println(s.otherpath); + } +} + +/** + * handler which should never be called + */ +class badarraysignalhandler implements DBusSigHandler { + /** + * Handling a signal + */ + public void handle(T s) { + test.fail("This signal handler shouldn't be called"); + } +} + +/** + * Callback handler + */ +class callbackhandler implements CallbackHandler { + public void handle(String r) { + System.out.println("Handling callback: " + r); + Collator col = Collator.getInstance(); + col.setDecomposition(Collator.FULL_DECOMPOSITION); + col.setStrength(Collator.PRIMARY); + if (0 != col.compare("This Is A UTF-8 Name: ﺱ !!", r)) + test.fail("call with callback, wrong return value"); + if (test.done4) test.fail("Already ran callback handler"); + test.done4 = true; + } + + public void handleError(DBusExecutionException e) { + System.out.println("Handling error callback: " + e + " message = '" + e.getMessage() + "'"); + if (!(e instanceof TestException)) test.fail("Exception is of the wrong sort"); + Collator col = Collator.getInstance(); + col.setDecomposition(Collator.FULL_DECOMPOSITION); + col.setStrength(Collator.PRIMARY); + if (0 != col.compare("test", e.getMessage())) + test.fail("Exception has the wrong message"); + if (test.done8) test.fail("Already ran callback error handler"); + test.done8 = true; + } +} + +/** + * This is a test program which sends and recieves a signal, implements, exports and calls a remote method. + */ +public class test { + public static boolean done1 = false; + public static boolean done2 = false; + public static boolean done3 = false; + public static boolean done4 = false; + public static boolean done5 = false; + public static boolean done6 = false; + public static boolean done7 = false; + public static boolean done8 = false; + + public static void fail(String message) { + System.out.println("Test Failed: " + message); + System.err.println("Test Failed: " + message); + if (null != serverconn) serverconn.disconnect(); + if (null != clientconn) clientconn.disconnect(); + System.exit(1); + } + + static DBusConnection serverconn = null; + static DBusConnection clientconn = null; + + @SuppressWarnings("unchecked") + public static void main(String[] args) { + try { + System.out.println("Creating Connection"); + serverconn = DBusConnection.getConnection(DBusConnection.SESSION); + clientconn = DBusConnection.getConnection(DBusConnection.SESSION); + serverconn.setWeakReferences(true); + clientconn.setWeakReferences(true); + + System.out.println("Registering Name"); + serverconn.requestBusName("foo.bar.Test"); + + /** This gets a remote object matching our bus name and exported object path. */ + Peer peer = clientconn.getRemoteObject("foo.bar.Test", "/Test", Peer.class); + DBus dbus = clientconn.getRemoteObject("org.freedesktop.DBus", "/org/freedesktop/DBus", DBus.class); + + System.out.print("Listening for signals..."); + signalhandler sigh = new signalhandler(); + renamedsignalhandler rsh = new renamedsignalhandler(); + try { + /** This registers an instance of the test class as the signal handler for the TestSignal class. */ + clientconn.addSigHandler(TestSignalInterface.EmptySignal.class, new emptysignalhandler()); + clientconn.addSigHandler(TestSignalInterface.TestSignal.class, sigh); + clientconn.addSigHandler(TestSignalInterface2.TestRenamedSignal.class, rsh); + clientconn.addSigHandler(DBus.Local.Disconnected.class, new disconnecthandler(clientconn, rsh)); + String source = dbus.GetNameOwner("foo.bar.Test"); + clientconn.addSigHandler(TestSignalInterface.TestArraySignal.class, source, peer, new arraysignalhandler()); + clientconn.addSigHandler(TestSignalInterface.TestObjectSignal.class, new objectsignalhandler()); + clientconn.addSigHandler(TestSignalInterface.TestPathSignal.class, new pathsignalhandler()); + badarraysignalhandler bash = new badarraysignalhandler(); + clientconn.addSigHandler(TestSignalInterface.TestSignal.class, bash); + clientconn.removeSigHandler(TestSignalInterface.TestSignal.class, bash); + System.out.println("done"); + } catch (MatchRuleInvalid MRI) { + test.fail("Failed to add handlers: " + MRI.getMessage()); + } catch (DBusException DBe) { + test.fail("Failed to add handlers: " + DBe.getMessage()); + } + + System.out.println("Listening for Method Calls"); + testclass tclass = new testclass(serverconn); + testclass tclass2 = new testclass(serverconn); + /** This exports an instance of the test class as the object /Test. */ + serverconn.exportObject("/Test", tclass); + serverconn.exportObject("/BadTest", tclass); + serverconn.exportObject("/BadTest2", tclass2); + serverconn.addFallback("/FallbackTest", tclass); + + // explicitly unexport object + serverconn.unExportObject("/BadTest"); + // implicitly unexport object + tclass2 = null; + System.gc(); + System.runFinalization(); + System.gc(); + System.runFinalization(); + System.gc(); + System.runFinalization(); + + System.out.println("Sending Signal"); + /** This creates an instance of the Test Signal, with the given object path, signal name and parameters, and broadcasts in on the Bus. */ + serverconn.sendSignal(new TestSignalInterface.TestSignal("/foo/bar/Wibble", "Bar", new UInt32(42))); + serverconn.sendSignal(new TestSignalInterface.EmptySignal("/foo/bar/Wibble")); + serverconn.sendSignal(new TestSignalInterface2.TestRenamedSignal("/foo/bar/Wibble", "Bar", new UInt32(42))); + + System.out.println("These things are on the bus:"); + String[] names = dbus.ListNames(); + for (String name : names) + System.out.println("\t" + name); + + System.out.println("Getting our introspection data"); + /** This gets a remote object matching our bus name and exported object path. */ + Introspectable intro = clientconn.getRemoteObject("foo.bar.Test", "/", Introspectable.class); + /** Get introspection data */ + String data;/* = intro.Introspect(); + if (null == data || !data.startsWith(" peers = serverconn.new PeerSet(); + peers.add("org.freedesktop.DBus"); + clientconn.requestBusName("test.testclient"); + peers.add("test.testclient"); + clientconn.releaseBusName("test.testclient"); + + System.out.println("Pinging ourselves"); + /** Call ping. */ + for (int i = 0; i < 10; i++) { + long then = System.currentTimeMillis(); + peer.Ping(); + long now = System.currentTimeMillis(); + System.out.println("Ping returned in " + (now - then) + "ms."); + } + + System.out.println("Calling Method0/1"); + /** This gets a remote object matching our bus name and exported object path. */ + TestRemoteInterface tri = (TestRemoteInterface) clientconn.getPeerRemoteObject("foo.bar.Test", "/Test"); + System.out.println("Got Remote Object: " + tri); + /** Call the remote object and get a response. */ + String rname = tri.getName(); + System.out.println("Got Remote Name: " + rname); + + Map svmmap = tri.svm(); + System.out.println(svmmap.toString()); + if (!"{ Parameters => [{ Name => [Joe],Password => [abcdef] }] }".equals(svmmap.toString())) + fail("incorrect reply from svm"); + + Path path = new Path("/nonexistantwooooooo"); + Path p = tri.pathrv(path); + System.out.println(path.toString() + " => " + p.toString()); + if (!path.equals(p)) fail("pathrv incorrect"); + List paths = new Vector(); + paths.add(path); + List ps = tri.pathlistrv(paths); + System.out.println(paths.toString() + " => " + ps.toString()); + if (!paths.equals(ps)) fail("pathlistrv incorrect"); + Map pathm = new HashMap(); + pathm.put(path, path); + Map pm = tri.pathmaprv(pathm); + System.out.println(pathm.toString() + " => " + pm.toString()); + System.out.println(pm.containsKey(path) + " " + pm.get(path) + " " + path.equals(pm.get(path))); + System.out.println(pm.containsKey(p) + " " + pm.get(p) + " " + p.equals(pm.get(p))); + for (Path q : pm.keySet()) { + System.out.println(q); + System.out.println(pm.get(q)); + } + if (!pm.containsKey(path) || !path.equals(pm.get(path))) fail("pathmaprv incorrect"); + + serverconn.sendSignal(new TestSignalInterface.TestPathSignal("/Test", path, paths, pathm)); + + Collator col = Collator.getInstance(); + col.setDecomposition(Collator.FULL_DECOMPOSITION); + col.setStrength(Collator.PRIMARY); + if (0 != col.compare("This Is A UTF-8 Name: ﺱ !!", rname)) + fail("getName return value incorrect"); + System.out.println("sending it to sleep"); + tri.waitawhile(); + System.out.println("testing floats"); + if (17.093f != tri.testfloat(new float[]{17.093f, -23f, 0.0f, 31.42f})) + fail("testfloat returned the wrong thing"); + System.out.println("Structs of Structs"); + List> lli = new Vector>(); + List li = new Vector(); + li.add(1); + li.add(2); + li.add(3); + lli.add(li); + lli.add(li); + lli.add(li); + TestStruct3 ts3 = new TestStruct3(new TestStruct2(new Vector(), new Variant(0)), lli); + int[][] out = tri.teststructstruct(ts3); + if (out.length != 3) fail("teststructstruct returned the wrong thing: " + Arrays.deepToString(out)); + for (int[] o : out) + if (o.length != 3 + || o[0] != 1 + || o[1] != 2 + || o[2] != 3) fail("teststructstruct returned the wrong thing: " + Arrays.deepToString(out)); + + System.out.println("frobnicating"); + List ls = new Vector(); + ls.add(2L); + ls.add(5L); + ls.add(71L); + Map mus = new HashMap(); + mus.put(new UInt16(4), (short) 5); + mus.put(new UInt16(5), (short) 6); + mus.put(new UInt16(6), (short) 7); + Map> msmus = new HashMap>(); + msmus.put("stuff", mus); + int rint = tri.frobnicate(ls, msmus, 13); + if (-5 != rint) + fail("frobnicate return value incorrect"); + + System.out.println("Doing stuff asynchronously with callback"); + clientconn.callWithCallback(tri, "getName", new callbackhandler()); + System.out.println("Doing stuff asynchronously with callback, which throws an error"); + clientconn.callWithCallback(tri, "getNameAndThrow", new callbackhandler()); + + /** call something that throws */ + try { + System.out.println("Throwing stuff"); + tri.throwme(); + test.fail("Method Execution should have failed"); + } catch (TestException Te) { + System.out.println("Remote Method Failed with: " + Te.getClass().getName() + " " + Te.getMessage()); + if (!Te.getMessage().equals("test")) + test.fail("Error message was not correct"); + } + + /* Test type signatures */ + Vector ts = new Vector(); + Marshalling.getJavaType("ya{si}", ts, -1); + tri.sig(ts.toArray(new Type[0])); + + tri.newpathtest(new Path("/new/path/test")); + + /** Try and call an invalid remote object */ + try { + System.out.println("Calling Method2"); + tri = clientconn.getRemoteObject("foo.bar.NotATest", "/Moofle", TestRemoteInterface.class); + System.out.println("Got Remote Name: " + tri.getName()); + test.fail("Method Execution should have failed"); + } catch (ServiceUnknown SU) { + System.out.println("Remote Method Failed with: " + SU.getClass().getName() + " " + SU.getMessage()); + } + + /** Try and call an invalid remote object */ + try { + System.out.println("Calling Method3"); + tri = clientconn.getRemoteObject("foo.bar.Test", "/Moofle", TestRemoteInterface.class); + System.out.println("Got Remote Name: " + tri.getName()); + test.fail("Method Execution should have failed"); + } catch (UnknownObject UO) { + System.out.println("Remote Method Failed with: " + UO.getClass().getName() + " " + UO.getMessage()); + } + + /** Try and call an explicitly unexported object */ + try { + System.out.println("Calling Method4"); + tri = clientconn.getRemoteObject("foo.bar.Test", "/BadTest", TestRemoteInterface.class); + System.out.println("Got Remote Name: " + tri.getName()); + test.fail("Method Execution should have failed"); + } catch (UnknownObject UO) { + System.out.println("Remote Method Failed with: " + UO.getClass().getName() + " " + UO.getMessage()); + } + + /** Try and call an implicitly unexported object */ + try { + System.out.println("Calling Method5"); + tri = clientconn.getRemoteObject("foo.bar.Test", "/BadTest2", TestRemoteInterface.class); + System.out.println("Got Remote Name: " + tri.getName()); + test.fail("Method Execution should have failed"); + } catch (UnknownObject UO) { + System.out.println("Remote Method Failed with: " + UO.getClass().getName() + " " + UO.getMessage()); + } + + System.out.println("Calling Method6"); + tri = clientconn.getRemoteObject("foo.bar.Test", "/FallbackTest/0/1", TestRemoteInterface.class); + intro = clientconn.getRemoteObject("foo.bar.Test", "/FallbackTest/0/4", Introspectable.class); + System.out.println("Got Fallback Name: " + tri.getName()); + System.out.println("Fallback Introspection Data: \n" + intro.Introspect()); + + System.out.println("Testing Properties returning Paths"); + Properties prop = clientconn.getRemoteObject("foo.bar.Test", "/Test", Properties.class); + Path prv = (Path) prop.Get("foo.bar", "foo"); + System.out.println("Got path " + prv); + System.out.println("Calling Method7--9"); + /** This gets a remote object matching our bus name and exported object path. */ + TestRemoteInterface2 tri2 = clientconn.getRemoteObject("foo.bar.Test", "/Test", TestRemoteInterface2.class); + System.out.print("Calling the other introspect method: "); + String intro2 = tri2.Introspect(); + System.out.println(intro2); + if (0 != col.compare("Not XML", intro2)) + fail("Introspect return value incorrect"); + + /** Call the remote object and get a response. */ + TestTuple, Boolean> rv = tri2.show(234); + System.out.println("Show returned: " + rv); + if (!serverconn.getUniqueName().equals(rv.a) || + 1 != rv.b.size() || + 1953 != rv.b.get(0) || + true != rv.c.booleanValue()) + fail("show return value incorrect (" + rv.a + "," + rv.b + "," + rv.c + ")"); + + System.out.println("Doing stuff asynchronously"); + DBusAsyncReply stuffreply = (DBusAsyncReply) clientconn.callMethodAsync(tri2, "dostuff", new TestStruct("bar", new UInt32(52), new Variant(new Boolean(true)))); + + System.out.println("Checking bools"); + if (tri2.check()) fail("bools are broken"); + + List l = new Vector(); + l.add("hi"); + l.add("hello"); + l.add("hej"); + l.add("hey"); + l.add("aloha"); + System.out.println("Sampling Arrays:"); + List is = tri2.sampleArray(l, new Integer[]{1, 5, 7, 9}, new long[]{2, 6, 8, 12}); + System.out.println("sampleArray returned an array:"); + for (Integer i : is) + System.out.println("--" + i); + if (is.size() != 5 || + is.get(0).intValue() != -1 || + is.get(1).intValue() != -5 || + is.get(2).intValue() != -7 || + is.get(3).intValue() != -12 || + is.get(4).intValue() != -18) + fail("sampleArray return value incorrect"); + + System.out.println("Get This"); + if (!tclass.equals(tri2.getThis(tri2))) + fail("Didn't get the correct this"); + + Boolean b = stuffreply.getReply(); + System.out.println("Do stuff replied " + b); + if (true != b.booleanValue()) + fail("dostuff return value incorrect"); + + System.out.print("Sending Array Signal..."); + /** This creates an instance of the Test Signal, with the given object path, signal name and parameters, and broadcasts in on the Bus. */ + List tsl = new Vector(); + tsl.add(new TestStruct2(l, new Variant(new UInt64(567)))); + Map tsm = new HashMap(); + tsm.put(new UInt32(1), new TestStruct2(l, new Variant(new UInt64(678)))); + tsm.put(new UInt32(42), new TestStruct2(l, new Variant(new UInt64(789)))); + serverconn.sendSignal(new TestSignalInterface.TestArraySignal("/Test", tsl, tsm)); + + System.out.println("done"); + + System.out.print("testing custom serialization..."); + Vector v = new Vector(); + v.add(1); + v.add(2); + v.add(3); + TestSerializable s = new TestSerializable(1, "woo", v); + s = tri2.testSerializable((byte) 12, s, 13); + System.out.print("returned: " + s); + if (s.getInt() != 1 || + !s.getString().equals("woo") || + s.getVector().size() != 3 || + s.getVector().get(0) != 1 || + s.getVector().get(1) != 2 || + s.getVector().get(2) != 3) + fail("Didn't get back the same TestSerializable"); + + System.out.println("done"); + + System.out.print("testing complex variants..."); + Map m = new HashMap(); + m.put("cow", "moo"); + tri2.complexv(new Variant(m, "a{ss}")); + System.out.println("done"); + + System.out.print("testing recursion..."); + + if (0 != col.compare("This Is A UTF-8 Name: ﺱ !!", tri2.recursionTest())) fail("recursion test failed"); + + System.out.println("done"); + + System.out.print("testing method overloading..."); + tri = clientconn.getRemoteObject("foo.bar.Test", "/Test", TestRemoteInterface.class); + if (1 != tri2.overload("foo")) test.fail("wrong overloaded method called"); + if (2 != tri2.overload((byte) 0)) test.fail("wrong overloaded method called"); + if (3 != tri2.overload()) test.fail("wrong overloaded method called"); + if (4 != tri.overload()) test.fail("wrong overloaded method called"); + System.out.println("done"); + + System.out.print("reg13291..."); + byte[] as = new byte[10]; + for (int i = 0; i < 10; i++) + as[i] = (byte) (100 - i); + tri.reg13291(as, as); + System.out.println("done"); + + System.out.print("Testing nested lists..."); + lli = new Vector>(); + li = new Vector(); + li.add(1); + lli.add(li); + List> reti = tri2.checklist(lli); + if (reti.size() != 1 || + reti.get(0).size() != 1 || + reti.get(0).get(0) != 1) + test.fail("Failed to check nested lists"); + System.out.println("done"); + + System.out.print("Testing dynamic object creation..."); + TestNewInterface tni = tri2.getNew(); + System.out.print(tni.getName() + " "); + System.out.println("done"); + + /* send an object in a signal */ + serverconn.sendSignal(new TestSignalInterface.TestObjectSignal("/foo/bar/Wibble", tclass)); + + /** Pause while we wait for the DBus messages to go back and forth. */ + Thread.sleep(1000); + + // check that bus name set has been trimmed + if (peers.size() != 1) fail("peers hasn't been trimmed"); + if (!peers.contains("org.freedesktop.DBus")) fail("peers contains the wrong name"); + + System.out.println("Checking for outstanding errors"); + DBusExecutionException DBEe = serverconn.getError(); + if (null != DBEe) throw DBEe; + DBEe = clientconn.getError(); + if (null != DBEe) throw DBEe; + + System.out.println("Disconnecting"); + /** Disconnect from the bus. */ + clientconn.disconnect(); + serverconn.disconnect(); + + System.out.println("Trying to do things after disconnection"); + + /** Remove sig handler */ + clientconn.removeSigHandler(TestSignalInterface.TestSignal.class, sigh); + + /** Call a method when disconnected */ + try { + System.out.println("getName() suceeded and returned: " + tri.getName()); + fail("Should not succeed when disconnected"); + } catch (NotConnected NC) { + System.out.println("getName() failed with exception " + NC); + } + clientconn = null; + serverconn = null; + + if (!done1) fail("Signal handler 1 failed to be run"); + if (!done2) fail("Signal handler 2 failed to be run"); + if (!done3) fail("Signal handler 3 failed to be run"); + if (!done4) fail("Callback handler failed to be run"); + if (!done5) fail("Signal handler R failed to be run"); + if (!done6) fail("Disconnect handler failed to be run"); + if (!done7) fail("Signal handler E failed to be run"); + if (!done8) fail("Error callback handler failed to be run"); + + } catch (Exception e) { + e.printStackTrace(); + fail("Unexpected Exception Occurred: " + e); + } + } +} diff --git a/federation/sssd/src/test/java/org/freedesktop/dbus/test_low_level.java b/federation/sssd/src/test/java/org/freedesktop/dbus/test_low_level.java new file mode 100644 index 0000000000..71699eb0a4 --- /dev/null +++ b/federation/sssd/src/test/java/org/freedesktop/dbus/test_low_level.java @@ -0,0 +1,50 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; + +import cx.ath.matthew.debug.Debug; + +public class test_low_level { + public static void main(String[] args) throws Exception { + Debug.setHexDump(true); + String addr = System.getenv("DBUS_SESSION_BUS_ADDRESS"); + Debug.print(addr); + BusAddress address = new BusAddress(addr); + Debug.print(address); + + Transport conn = new Transport(address); + + Message m = new MethodCall("org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", "Hello", (byte) 0, null); + conn.mout.writeMessage(m); + m = conn.min.readMessage(); + Debug.print(m.getClass()); + Debug.print(m); + m = conn.min.readMessage(); + Debug.print(m.getClass()); + Debug.print(m); + m = conn.min.readMessage(); + Debug.print("" + m); + m = new MethodCall("org.freedesktop.DBus", "/", null, "Hello", (byte) 0, null); + conn.mout.writeMessage(m); + m = conn.min.readMessage(); + Debug.print(m); + + m = new MethodCall("org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", "RequestName", (byte) 0, "su", "org.testname", 0); + conn.mout.writeMessage(m); + m = conn.min.readMessage(); + Debug.print(m); + m = new DBusSignal(null, "/foo", "org.foo", "Foo", null); + conn.mout.writeMessage(m); + m = conn.min.readMessage(); + Debug.print(m); + conn.disconnect(); + } +} diff --git a/federation/sssd/src/test/java/org/freedesktop/dbus/two_part_test_client.java b/federation/sssd/src/test/java/org/freedesktop/dbus/two_part_test_client.java new file mode 100644 index 0000000000..75f4bd843a --- /dev/null +++ b/federation/sssd/src/test/java/org/freedesktop/dbus/two_part_test_client.java @@ -0,0 +1,43 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; + +public class two_part_test_client { + public static class two_part_test_object implements TwoPartObject { + public boolean isRemote() { + return false; + } + + public String getName() { + System.out.println("client name"); + return toString(); + } + } + + public static void main(String[] args) throws Exception { + System.out.println("get conn"); + DBusConnection conn = DBusConnection.getConnection(DBusConnection.SESSION); + System.out.println("get remote"); + TwoPartInterface remote = conn.getRemoteObject("org.freedesktop.dbus.test.two_part_server", "/", TwoPartInterface.class); + System.out.println("get object"); + TwoPartObject o = remote.getNew(); + System.out.println("get name"); + System.out.println(o.getName()); + two_part_test_object tpto = new two_part_test_object(); + conn.exportObject("/TestObject", tpto); + conn.sendSignal(new TwoPartInterface.TwoPartSignal("/FromObject", tpto)); + try { + Thread.sleep(1000); + } catch (InterruptedException Ie) { + } + conn.disconnect(); + } +} diff --git a/federation/sssd/src/test/java/org/freedesktop/dbus/two_part_test_server.java b/federation/sssd/src/test/java/org/freedesktop/dbus/two_part_test_server.java new file mode 100644 index 0000000000..c0b1ded1eb --- /dev/null +++ b/federation/sssd/src/test/java/org/freedesktop/dbus/two_part_test_server.java @@ -0,0 +1,62 @@ +/* + D-Bus Java Implementation + Copyright (c) 2005-2006 Matthew Johnson + + This program is free software; you can redistribute it and/or modify it + under the terms of either the GNU Lesser General Public License Version 2 or the + Academic Free Licence Version 2.1. + + Full licence texts are included in the COPYING file with this program. +*/ +package org.freedesktop.dbus; + +public class two_part_test_server implements TwoPartInterface, DBusSigHandler { + public class two_part_test_object implements TwoPartObject { + public boolean isRemote() { + return false; + } + + public String getName() { + System.out.println("give name"); + return toString(); + } + } + + private DBusConnection conn; + + public two_part_test_server(DBusConnection conn) { + this.conn = conn; + } + + public boolean isRemote() { + return false; + } + + public TwoPartObject getNew() { + TwoPartObject o = new two_part_test_object(); + System.out.println("export new"); + try { + conn.exportObject("/12345", o); + } catch (Exception e) { + } + System.out.println("give new"); + return o; + } + + public void handle(TwoPartInterface.TwoPartSignal s) { + System.out.println("Got: " + s.o); + } + + public static void main(String[] args) throws Exception { + DBusConnection conn = DBusConnection.getConnection(DBusConnection.SESSION); + conn.requestBusName("org.freedesktop.dbus.test.two_part_server"); + two_part_test_server server = new two_part_test_server(conn); + conn.exportObject("/", server); + conn.addSigHandler(TwoPartInterface.TwoPartSignal.class, server); + while (true) try { + Thread.sleep(10000); + } catch (InterruptedException Ie) { + } + } +} + diff --git a/federation/sssd/src/test/java/org/jvnet/libpam/InteractiveTester.java b/federation/sssd/src/test/java/org/jvnet/libpam/InteractiveTester.java new file mode 100644 index 0000000000..47fef180b5 --- /dev/null +++ b/federation/sssd/src/test/java/org/jvnet/libpam/InteractiveTester.java @@ -0,0 +1,95 @@ +/* + * The MIT License + * + * Copyright (c) 2009, Sun Microsystems, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.jvnet.libpam; + +import junit.framework.TestCase; + +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; + +public class InteractiveTester extends TestCase { + public InteractiveTester(String testName) { + super(testName); + } + + public void testPositiveCase() throws Exception { + for (int i = 0; i < 1000; i++) + testOne(); + } + + public void testOne() throws Exception { + UnixUser u = new PAM("sshd").authenticate(System.getProperty("user.name"), System.getProperty("password")); + if (!printOnce) { + System.out.println(u.getUID()); + System.out.println(u.getGroups()); + printOnce = true; + } + } + + public void testGetGroups() throws Exception { + System.out.println(new PAM("sshd").getGroupsOfUser(System.getProperty("user.name"))); + } + + public void testConcurrent() throws Exception { + ExecutorService es = Executors.newFixedThreadPool(10); + Set> result = new HashSet>(); + for (int i = 0; i < 1000; i++) { + result.add(es.submit(new Callable() { + public Object call() throws Exception { + testOne(); + return null; + } + })); + } + // wait for completion + for (Future f : result) { + f.get(); + } + es.shutdown(); + } + + public void testNegative() throws Exception { + try { + new PAM("sshd").authenticate("bogus", "bogus"); + fail("expected a failure"); + } catch (PAMException e) { + // yep + } + } + + public static void main(String[] args) throws Exception { + UnixUser u = new PAM("sshd").authenticate(args[0], args[1]); + System.out.println(u.getUID()); + System.out.println(u.getGroups()); + System.out.println(u.getGecos()); + System.out.println(u.getDir()); + System.out.println(u.getShell()); + } + + private boolean printOnce; +} diff --git a/federation/sssd/src/test/java/org/jvnet/libpam/UnixUserTest.java b/federation/sssd/src/test/java/org/jvnet/libpam/UnixUserTest.java new file mode 100644 index 0000000000..4b3e5f5672 --- /dev/null +++ b/federation/sssd/src/test/java/org/jvnet/libpam/UnixUserTest.java @@ -0,0 +1,52 @@ +/* + * The MIT License + * + * Copyright (c) 2014, R. Tyler Croy + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.jvnet.libpam; + +import junit.framework.Assert; +import org.junit.Before; +import org.junit.Test; + +public class UnixUserTest { + private UnixUser user = null; + + @Before + public void setUp() throws PAMException { + user = new UnixUser("root"); + } + + @Test + public void testGetUserName() { + Assert.assertEquals("root", user.getUserName()); + } + + @Test + public void testGetDir() { + Assert.assertNotNull(user.getDir()); + } + + @Test + public void testGetUID() { + Assert.assertEquals(0, user.getUID()); + } +} diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ResourcesResource.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ResourcesResource.java index 1aaaa2352c..07438d07f4 100644 --- a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ResourcesResource.java +++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ResourcesResource.java @@ -25,6 +25,7 @@ import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import java.util.List; @@ -42,6 +43,17 @@ public interface ResourcesResource { @Path("{id}") ResourceResource resource(@PathParam("id") String id); + @GET + @NoCache + @Produces(MediaType.APPLICATION_JSON) + List find(@QueryParam("name") String name, + @QueryParam("uri") String uri, + @QueryParam("owner") String owner, + @QueryParam("type") String type, + @QueryParam("scope") String scope, + @QueryParam("first") Integer firstResult, + @QueryParam("max") Integer maxResult); + @GET @NoCache @Produces(MediaType.APPLICATION_JSON) diff --git a/misc/HackingOnKeycloak.md b/misc/HackingOnKeycloak.md index 590a301983..902d1edf97 100644 --- a/misc/HackingOnKeycloak.md +++ b/misc/HackingOnKeycloak.md @@ -59,5 +59,6 @@ Here's a quick check list for a good pull request (PR): * A JIRA associated with your PR (include the JIRA issue number in commit comment) * All tests in testsuite pass * Do a rebase on upstream master +* We only accept contributions to the master branch. The exception to this is if the fix is for the latest CR release and Final has not yet been released, in which case you can send the PR to both the corresponding branch and the master branch. -Once you're happy with your changes go to GitHub and create a PR. +Once you're happy with your changes go to GitHub and create a PR to the master branch. diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanCacheUserProviderFactory.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanUserCacheProviderFactory.java similarity index 79% rename from model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanCacheUserProviderFactory.java rename to model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanUserCacheProviderFactory.java index e0409b1aa5..c5205381bf 100755 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanCacheUserProviderFactory.java +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanUserCacheProviderFactory.java @@ -18,34 +18,28 @@ package org.keycloak.models.cache.infinispan; import org.infinispan.Cache; -import org.infinispan.notifications.Listener; -import org.infinispan.notifications.cachelistener.annotation.*; -import org.infinispan.notifications.cachelistener.event.*; import org.jboss.logging.Logger; import org.keycloak.Config; import org.keycloak.connections.infinispan.InfinispanConnectionProvider; import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSessionFactory; -import org.keycloak.models.cache.CacheUserProvider; -import org.keycloak.models.cache.CacheUserProviderFactory; -import org.keycloak.models.cache.infinispan.entities.CachedUser; +import org.keycloak.models.cache.UserCache; +import org.keycloak.models.cache.UserCacheProviderFactory; import org.keycloak.models.cache.infinispan.entities.Revisioned; -import java.util.concurrent.ConcurrentHashMap; - /** * @author Stian Thorgersen */ -public class InfinispanCacheUserProviderFactory implements CacheUserProviderFactory { +public class InfinispanUserCacheProviderFactory implements UserCacheProviderFactory { - private static final Logger log = Logger.getLogger(InfinispanCacheUserProviderFactory.class); + private static final Logger log = Logger.getLogger(InfinispanUserCacheProviderFactory.class); protected volatile UserCacheManager userCache; @Override - public CacheUserProvider create(KeycloakSession session) { + public UserCache create(KeycloakSession session) { lazyInit(session); return new UserCacheSession(userCache, session); } diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java index 8256aa221d..6950ed8496 100755 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java @@ -1465,4 +1465,64 @@ public class RealmAdapter implements RealmModel { if (isUpdated()) return updated.getComponent(id); return cached.getComponents().get(id); } + + public void setAttribute(String name, String value) { + getDelegateForUpdate(); + updated.setAttribute(name, value); + } + + @Override + public void setAttribute(String name, Boolean value) { + getDelegateForUpdate(); + updated.setAttribute(name, value); + } + + @Override + public void setAttribute(String name, Integer value) { + getDelegateForUpdate(); + updated.setAttribute(name, value); + } + + @Override + public void setAttribute(String name, Long value) { + getDelegateForUpdate(); + updated.setAttribute(name, value); + } + + @Override + public void removeAttribute(String name) { + getDelegateForUpdate(); + updated.removeAttribute(name); + } + + @Override + public String getAttribute(String name) { + if (isUpdated()) return updated.getAttribute(name); + return cached.getAttribute(name); + } + + @Override + public Integer getAttribute(String name, Integer defaultValue) { + if (isUpdated()) return updated.getAttribute(name, defaultValue); + return cached.getAttribute(name, defaultValue); + } + + @Override + public Long getAttribute(String name, Long defaultValue) { + if (isUpdated()) return updated.getAttribute(name, defaultValue); + return cached.getAttribute(name, defaultValue); + } + + @Override + public Boolean getAttribute(String name, Boolean defaultValue) { + if (isUpdated()) return updated.getAttribute(name, defaultValue); + return cached.getAttribute(name, defaultValue); + } + + @Override + public Map getAttributes() { + if (isUpdated()) return updated.getAttributes(); + return cached.getAttributes(); + } + } diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserAdapter.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserAdapter.java index d697ad236e..ea6698cf83 100755 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserAdapter.java +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserAdapter.java @@ -28,6 +28,7 @@ import org.keycloak.models.UserConsentModel; import org.keycloak.models.UserCredentialModel; import org.keycloak.models.UserCredentialValueModel; import org.keycloak.models.UserModel; +import org.keycloak.models.cache.CachedUserModel; import org.keycloak.models.cache.infinispan.entities.CachedUser; import org.keycloak.models.cache.infinispan.entities.CachedUserConsent; import org.keycloak.models.utils.KeycloakModelUtils; @@ -39,12 +40,13 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; /** * @author Bill Burke * @version $Revision: 1 $ */ -public class UserAdapter implements UserModel { +public class UserAdapter implements CachedUserModel { protected UserModel updated; protected CachedUser cached; protected UserCacheSession userProviderCache; @@ -65,6 +67,22 @@ public class UserAdapter implements UserModel { if (updated == null) throw new IllegalStateException("Not found in database"); } } + + @Override + public void invalidate() { + getDelegateForUpdate(); + } + + @Override + public long getCacheTimestamp() { + return cached.getCacheTimestamp(); + } + + @Override + public ConcurrentHashMap getCachedWith() { + return cached.getCachedWith(); + } + @Override public String getId() { if (updated != null) return updated.getId(); diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserCacheSession.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserCacheSession.java index 8a62c3337e..b38d9aab1b 100755 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserCacheSession.java +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserCacheSession.java @@ -34,7 +34,9 @@ import org.keycloak.models.UserCredentialModel; import org.keycloak.models.UserFederationProviderModel; import org.keycloak.models.UserModel; import org.keycloak.models.UserProvider; -import org.keycloak.models.cache.CacheUserProvider; +import org.keycloak.models.cache.CachedUserModel; +import org.keycloak.models.cache.OnUserCache; +import org.keycloak.models.cache.UserCache; import org.keycloak.models.cache.infinispan.entities.CachedFederatedIdentityLinks; import org.keycloak.models.cache.infinispan.entities.CachedUser; import org.keycloak.models.cache.infinispan.entities.CachedUserConsent; @@ -47,7 +49,7 @@ import java.util.*; * @author Bill Burke * @version $Revision: 1 $ */ -public class UserCacheSession implements CacheUserProvider { +public class UserCacheSession implements UserCache { protected static final Logger logger = Logger.getLogger(UserCacheSession.class); protected UserCacheManager cache; protected KeycloakSession session; @@ -73,7 +75,6 @@ public class UserCacheSession implements CacheUserProvider { cache.clear(); } - @Override public UserProvider getDelegate() { if (!transactionActive) throw new IllegalStateException("Cannot access delegate without a transaction"); if (delegate != null) return delegate; @@ -89,6 +90,20 @@ public class UserCacheSession implements CacheUserProvider { if (realm.isIdentityFederationEnabled()) invalidations.add(getFederatedIdentityLinksCacheKey(user.getId())); } + @Override + public void evict(RealmModel realm, UserModel user) { + if (user instanceof CachedUserModel) { + ((CachedUserModel)user).invalidate(); + } else { + invalidations.add(user.getId()); + if (user.getEmail() != null) invalidations.add(getUserByEmailCacheKey(realm.getId(), user.getEmail())); + invalidations.add(getUserByUsernameCacheKey(realm.getId(), user.getUsername())); + if (realm.isIdentityFederationEnabled()) invalidations.add(getFederatedIdentityLinksCacheKey(user.getId())); + } + } + + + protected void runInvalidations() { for (String realmId : realmInvalidations) { cache.invalidateRealmUsers(realmId, invalidations); @@ -147,8 +162,13 @@ public class UserCacheSession implements CacheUserProvider { logger.trace("registered for invalidation return delegate"); return getDelegate().getUserById(id, realm); } + if (managedUsers.containsKey(id)) { + logger.trace("return managedusers"); + return managedUsers.get(id); + } CachedUser cached = cache.get(id, CachedUser.class); + boolean wasCached = cached != null; if (cached == null) { logger.trace("not cached"); Long loaded = cache.getCurrentRevision(id); @@ -157,19 +177,12 @@ public class UserCacheSession implements CacheUserProvider { logger.trace("delegate returning null"); return null; } - if (managedUsers.containsKey(id)) { - logger.trace("return managedusers"); - return managedUsers.get(id); - } - if (invalidations.contains(id)) return model; cached = new CachedUser(loaded, realm, model); cache.addRevisioned(cached, startupRevision); - } else if (managedUsers.containsKey(id)) { - logger.trace("return managedusers"); - return managedUsers.get(id); } logger.trace("returning new cache adapter"); UserAdapter adapter = new UserAdapter(cached, this, session, realm); + if (!wasCached) onCache(realm, adapter); managedUsers.put(id, adapter); return adapter; } @@ -223,13 +236,7 @@ public class UserCacheSession implements CacheUserProvider { return managedUsers.get(userId); } - CachedUser cached = cache.get(userId, CachedUser.class); - if (cached == null) { - cached = new CachedUser(loaded, realm, model); - cache.addRevisioned(cached, startupRevision); - } - logger.trace("return new cache adapter"); - UserAdapter adapter = new UserAdapter(cached, this, session, realm); + UserAdapter adapter = getUserAdapter(realm, userId, loaded, model); managedUsers.put(userId, adapter); return adapter; } else { @@ -244,6 +251,26 @@ public class UserCacheSession implements CacheUserProvider { } } + protected UserAdapter getUserAdapter(RealmModel realm, String userId, Long loaded, UserModel model) { + CachedUser cached = cache.get(userId, CachedUser.class); + boolean wasCached = cached != null; + if (cached == null) { + cached = new CachedUser(loaded, realm, model); + cache.addRevisioned(cached, startupRevision); + } + UserAdapter adapter = new UserAdapter(cached, this, session, realm); + if (!wasCached) { + onCache(realm, adapter); + } + return adapter; + + } + + private void onCache(RealmModel realm, UserAdapter adapter) { + ((OnUserCache)getDelegate()).onCache(realm, adapter); + ((OnUserCache)session.userCredentialManager()).onCache(realm, adapter); + } + @Override public UserModel getUserByEmail(String email, RealmModel realm) { if (email == null) return null; @@ -268,12 +295,7 @@ public class UserCacheSession implements CacheUserProvider { if (invalidations.contains(userId)) return model; if (managedUsers.containsKey(userId)) return managedUsers.get(userId); - CachedUser cached = cache.get(userId, CachedUser.class); - if (cached == null) { - cached = new CachedUser(loaded, realm, model); - cache.addRevisioned(cached, startupRevision); - } - UserAdapter adapter = new UserAdapter(cached, this, session, realm); + UserAdapter adapter = getUserAdapter(realm, userId, loaded, model); managedUsers.put(userId, adapter); return adapter; } else { @@ -316,12 +338,7 @@ public class UserCacheSession implements CacheUserProvider { if (invalidations.contains(userId)) return model; if (managedUsers.containsKey(userId)) return managedUsers.get(userId); - CachedUser cached = cache.get(userId, CachedUser.class); - if (cached == null) { - cached = new CachedUser(loaded, realm, model); - cache.addRevisioned(cached, startupRevision); - } - UserAdapter adapter = new UserAdapter(cached, this, session, realm); + UserAdapter adapter = getUserAdapter(realm, userId, loaded, model); managedUsers.put(userId, adapter); return adapter; } else { @@ -669,4 +686,5 @@ public class UserCacheSession implements CacheUserProvider { getDelegate().preRemove(realm, component); } + } diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/AbstractExtendableRevisioned.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/AbstractExtendableRevisioned.java new file mode 100644 index 0000000000..9940732841 --- /dev/null +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/AbstractExtendableRevisioned.java @@ -0,0 +1,40 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.keycloak.models.cache.infinispan.entities; + +import java.util.concurrent.ConcurrentHashMap; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public abstract class AbstractExtendableRevisioned extends AbstractRevisioned { + protected ConcurrentHashMap cachedWith = new ConcurrentHashMap(); + + public AbstractExtendableRevisioned(Long revision, String id) { + super(revision, id); + } + + /** + * Cache things along with this cachable object + * + * @return + */ + public ConcurrentHashMap getCachedWith() { + return cachedWith; + } +} diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/AbstractRevisioned.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/AbstractRevisioned.java index 89166202c5..ed49ddff1e 100755 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/AbstractRevisioned.java +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/AbstractRevisioned.java @@ -9,6 +9,7 @@ import java.io.Serializable; public class AbstractRevisioned implements Revisioned, Serializable { private String id; private Long revision; + private final long cacheTimestamp = System.currentTimeMillis(); public AbstractRevisioned(Long revision, String id) { this.revision = revision; @@ -30,4 +31,12 @@ public class AbstractRevisioned implements Revisioned, Serializable { this.revision = revision; } + /** + * When was this cached + * + * @return + */ + public long getCacheTimestamp() { + return cacheTimestamp; + } } diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedRealm.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedRealm.java index 7eab0ebac2..e85078e4c4 100755 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedRealm.java +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedRealm.java @@ -152,6 +152,8 @@ public class CachedRealm extends AbstractRevisioned { protected MultivaluedHashMap identityProviderMappers = new MultivaluedHashMap<>(); protected Set identityProviderMapperSet; + protected Map attributes; + public CachedRealm(Long revision, RealmModel model) { super(revision, model.getId()); name = model.getName(); @@ -231,10 +233,10 @@ public class CachedRealm extends AbstractRevisioned { eventsExpiration = model.getEventsExpiration(); eventsListeners = model.getEventsListeners(); enabledEventTypes = model.getEnabledEventTypes(); - + adminEventsEnabled = model.isAdminEventsEnabled(); adminEventsDetailsEnabled = model.isAdminEventsDetailsEnabled(); - + defaultRoles = model.getDefaultRoles(); ClientModel masterAdminClient = model.getMasterAdminClient(); this.masterAdminClient = (masterAdminClient != null) ? masterAdminClient.getId() : null; @@ -285,6 +287,11 @@ public class CachedRealm extends AbstractRevisioned { components.put(component.getId(), component); } + try { + attributes = model.getAttributes(); + } catch (UnsupportedOperationException ex) { + } + } protected void cacheClientTemplates(RealmModel model) { @@ -475,7 +482,7 @@ public class CachedRealm extends AbstractRevisioned { public Set getEventsListeners() { return eventsListeners; } - + public Set getEnabledEventTypes() { return enabledEventTypes; } @@ -619,4 +626,28 @@ public class CachedRealm extends AbstractRevisioned { public Map getComponents() { return components; } + + public String getAttribute(String name) { + return attributes != null ? attributes.get(name) : null; + } + + public Integer getAttribute(String name, Integer defaultValue) { + String v = getAttribute(name); + return v != null ? Integer.parseInt(v) : defaultValue; + } + + public Long getAttribute(String name, Long defaultValue) { + String v = getAttribute(name); + return v != null ? Long.parseLong(v) : defaultValue; + } + + public Boolean getAttribute(String name, Boolean defaultValue) { + String v = getAttribute(name); + return v != null ? Boolean.parseBoolean(v) : defaultValue; + } + + public Map getAttributes() { + return attributes; + } + } diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedUser.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedUser.java index 89049cae99..7c24594885 100755 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedUser.java +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedUser.java @@ -36,7 +36,7 @@ import java.util.Set; * @author Bill Burke * @version $Revision: 1 $ */ -public class CachedUser extends AbstractRevisioned implements InRealm { +public class CachedUser extends AbstractExtendableRevisioned implements InRealm { private String realm; private String username; private Long createdTimestamp; diff --git a/model/infinispan/src/main/resources/META-INF/services/org.keycloak.models.cache.CacheUserProviderFactory b/model/infinispan/src/main/resources/META-INF/services/org.keycloak.models.cache.UserCacheProviderFactory similarity index 91% rename from model/infinispan/src/main/resources/META-INF/services/org.keycloak.models.cache.CacheUserProviderFactory rename to model/infinispan/src/main/resources/META-INF/services/org.keycloak.models.cache.UserCacheProviderFactory index f680e3a4c3..e558cf21e9 100755 --- a/model/infinispan/src/main/resources/META-INF/services/org.keycloak.models.cache.CacheUserProviderFactory +++ b/model/infinispan/src/main/resources/META-INF/services/org.keycloak.models.cache.UserCacheProviderFactory @@ -15,4 +15,4 @@ # limitations under the License. # -org.keycloak.models.cache.infinispan.InfinispanCacheUserProviderFactory \ No newline at end of file +org.keycloak.models.cache.infinispan.InfinispanUserCacheProviderFactory \ No newline at end of file diff --git a/model/infinispan/src/test/java/org/keycloak/models/sessions/infinispan/initializer/ClusteredCacheBehaviorTest.java b/model/infinispan/src/test/java/org/keycloak/models/sessions/infinispan/initializer/ClusteredCacheBehaviorTest.java index de6e587d0a..4c5ccc47d0 100755 --- a/model/infinispan/src/test/java/org/keycloak/models/sessions/infinispan/initializer/ClusteredCacheBehaviorTest.java +++ b/model/infinispan/src/test/java/org/keycloak/models/sessions/infinispan/initializer/ClusteredCacheBehaviorTest.java @@ -101,14 +101,19 @@ public class ClusteredCacheBehaviorTest { System.out.println("node1 create entry"); node1Cache.put("key", "node1"); + System.out.println("node1 create entry"); node1Cache.put("key", "node111"); + System.out.println("node2 create entry"); node2Cache.put("key", "node2"); + System.out.println("node1 remove entry"); node1Cache.remove("key"); + System.out.println("node2 remove entry"); node2Cache.remove("key"); + System.out.println("node2 put entry"); node2Cache.put("key", "node2"); System.out.println("node2 evict entry"); @@ -118,6 +123,28 @@ public class ClusteredCacheBehaviorTest { node2Cache.putForExternalRead("key", "common"); System.out.println("node2 remove entry"); node2Cache.remove("key"); + System.out.println("node1 remove entry"); + node1Cache.remove("key"); + + // test remove non-existing node 2, existing node 1 + System.out.println("Test non existent remove"); + System.out.println("node1 create entry"); + node1Cache.put("key", "value"); + System.out.println("node2 remove non-existent entry"); + System.out.println("exists?: " + node2Cache.containsKey("key")); + node2Cache.remove("key"); + + // test clear + System.out.println("Test clear cache"); + System.out.println("add key to node 1, key2 to node2"); + node1Cache.putForExternalRead("key", "value"); + node2Cache.putForExternalRead("key", "value"); + node2Cache.putForExternalRead("key2", "value"); + System.out.println("Clear from node1"); + node1Cache.clear(); + System.out.println("node 2 exists key2?: " + node2Cache.containsKey("key2")); + System.out.println("node 2 exists key?: " + node2Cache.containsKey("key")); + } diff --git a/model/jpa/src/main/java/org/keycloak/connections/jpa/DefaultJpaConnectionProvider.java b/model/jpa/src/main/java/org/keycloak/connections/jpa/DefaultJpaConnectionProvider.java index db85a825c9..e35e5b0734 100644 --- a/model/jpa/src/main/java/org/keycloak/connections/jpa/DefaultJpaConnectionProvider.java +++ b/model/jpa/src/main/java/org/keycloak/connections/jpa/DefaultJpaConnectionProvider.java @@ -17,6 +17,8 @@ package org.keycloak.connections.jpa; +import org.jboss.logging.Logger; + import javax.persistence.EntityManager; /** @@ -24,6 +26,7 @@ import javax.persistence.EntityManager; */ public class DefaultJpaConnectionProvider implements JpaConnectionProvider { + private static final Logger logger = Logger.getLogger(DefaultJpaConnectionProvider.class); private final EntityManager em; public DefaultJpaConnectionProvider(EntityManager em) { @@ -37,6 +40,7 @@ public class DefaultJpaConnectionProvider implements JpaConnectionProvider { @Override public void close() { + logger.trace("DefaultJpaConnectionProvider close()"); em.close(); } diff --git a/model/jpa/src/main/java/org/keycloak/connections/jpa/DefaultJpaConnectionProviderFactory.java b/model/jpa/src/main/java/org/keycloak/connections/jpa/DefaultJpaConnectionProviderFactory.java index 3c74264f8d..d029e16767 100755 --- a/model/jpa/src/main/java/org/keycloak/connections/jpa/DefaultJpaConnectionProviderFactory.java +++ b/model/jpa/src/main/java/org/keycloak/connections/jpa/DefaultJpaConnectionProviderFactory.java @@ -29,12 +29,22 @@ import java.util.Map; import javax.naming.InitialContext; import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; +import javax.persistence.SynchronizationType; import javax.sql.DataSource; +import javax.transaction.InvalidTransactionException; +import javax.transaction.Synchronization; +import javax.transaction.SystemException; +import javax.transaction.Transaction; +import javax.transaction.TransactionManager; +import javax.transaction.UserTransaction; import org.hibernate.ejb.AvailableSettings; +import org.hibernate.engine.transaction.jta.platform.internal.AbstractJtaPlatform; +import org.hibernate.engine.transaction.jta.platform.spi.JtaPlatform; import org.jboss.logging.Logger; import org.keycloak.Config; import org.keycloak.connections.jpa.updater.JpaUpdaterProvider; +import org.keycloak.connections.jpa.updater.liquibase.conn.LiquibaseConnectionProvider; import org.keycloak.connections.jpa.util.JpaUtils; import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSessionFactory; @@ -45,6 +55,7 @@ import org.keycloak.provider.ServerInfoAwareProviderFactory; import org.keycloak.models.dblock.DBLockManager; import org.keycloak.ServerStartupError; import org.keycloak.timer.TimerProvider; +import org.keycloak.transaction.JtaTransactionManagerLookup; /** * @author Stian Thorgersen @@ -60,16 +71,29 @@ public class DefaultJpaConnectionProviderFactory implements JpaConnectionProvide private volatile EntityManagerFactory emf; private Config.Scope config; - - private Map operationalInfo; + + private Map operationalInfo; + + private boolean jtaEnabled; + private JtaTransactionManagerLookup jtaLookup; + + private KeycloakSessionFactory factory; @Override public JpaConnectionProvider create(KeycloakSession session) { + logger.trace("Create JpaConnectionProvider"); lazyInit(session); - EntityManager em = emf.createEntityManager(); + EntityManager em = null; + if (!jtaEnabled) { + logger.trace("enlisting EntityManager in JpaKeycloakTransaction"); + em = emf.createEntityManager(); + } else { + + em = emf.createEntityManager(SynchronizationType.SYNCHRONIZED); + } em = PersistenceExceptionConverter.create(em); - session.getTransactionManager().enlist(new JpaKeycloakTransaction(em)); + if (!jtaEnabled) session.getTransactionManager().enlist(new JpaKeycloakTransaction(em)); return new DefaultJpaConnectionProvider(em); } @@ -92,85 +116,112 @@ public class DefaultJpaConnectionProviderFactory implements JpaConnectionProvide @Override public void postInit(KeycloakSessionFactory factory) { + this.factory = factory; + checkJtaEnabled(factory); } + protected void checkJtaEnabled(KeycloakSessionFactory factory) { + jtaLookup = (JtaTransactionManagerLookup) factory.getProviderFactory(JtaTransactionManagerLookup.class); + if (jtaLookup != null) { + if (jtaLookup.getTransactionManager() != null) { + jtaEnabled = true; + } + } + } + private void lazyInit(KeycloakSession session) { if (emf == null) { synchronized (this) { if (emf == null) { - logger.debug("Initializing JPA connections"); + KeycloakModelUtils.suspendJtaTransaction(session.getKeycloakSessionFactory(), () -> { + logger.debug("Initializing JPA connections"); - Map properties = new HashMap(); + Map properties = new HashMap(); - String unitName = "keycloak-default"; + String unitName = "keycloak-default"; - String dataSource = config.get("dataSource"); - if (dataSource != null) { - if (config.getBoolean("jta", false)) { - properties.put(AvailableSettings.JTA_DATASOURCE, dataSource); + String dataSource = config.get("dataSource"); + if (dataSource != null) { + if (config.getBoolean("jta", jtaEnabled)) { + properties.put(AvailableSettings.JTA_DATASOURCE, dataSource); + } else { + properties.put(AvailableSettings.NON_JTA_DATASOURCE, dataSource); + } } else { - properties.put(AvailableSettings.NON_JTA_DATASOURCE, dataSource); - } - } else { - properties.put(AvailableSettings.JDBC_URL, config.get("url")); - properties.put(AvailableSettings.JDBC_DRIVER, config.get("driver")); + properties.put(AvailableSettings.JDBC_URL, config.get("url")); + properties.put(AvailableSettings.JDBC_DRIVER, config.get("driver")); - String user = config.get("user"); - if (user != null) { - properties.put(AvailableSettings.JDBC_USER, user); - } - String password = config.get("password"); - if (password != null) { - properties.put(AvailableSettings.JDBC_PASSWORD, password); - } - } - - String schema = getSchema(); - if (schema != null) { - properties.put(JpaUtils.HIBERNATE_DEFAULT_SCHEMA, schema); - } - - MigrationStrategy migrationStrategy = getMigrationStrategy(); - boolean initializeEmpty = config.getBoolean("initializeEmpty", true); - File databaseUpdateFile = getDatabaseUpdateFile(); - - properties.put("hibernate.show_sql", config.getBoolean("showSql", false)); - properties.put("hibernate.format_sql", config.getBoolean("formatSql", true)); - - Connection connection = getConnection(); - try{ - prepareOperationalInfo(connection); - - String driverDialect = detectDialect(connection); - if (driverDialect != null) { - properties.put("hibernate.dialect", driverDialect); + String user = config.get("user"); + if (user != null) { + properties.put(AvailableSettings.JDBC_USER, user); + } + String password = config.get("password"); + if (password != null) { + properties.put(AvailableSettings.JDBC_PASSWORD, password); + } } - migration(migrationStrategy, initializeEmpty, schema, databaseUpdateFile, connection, session); - - int globalStatsInterval = config.getInt("globalStatsInterval", -1); - if (globalStatsInterval != -1) { - properties.put("hibernate.generate_statistics", true); + String schema = getSchema(); + if (schema != null) { + properties.put(JpaUtils.HIBERNATE_DEFAULT_SCHEMA, schema); } - logger.trace("Creating EntityManagerFactory"); - emf = JpaUtils.createEntityManagerFactory(session, unitName, properties, getClass().getClassLoader()); - logger.trace("EntityManagerFactory created"); + MigrationStrategy migrationStrategy = getMigrationStrategy(); + boolean initializeEmpty = config.getBoolean("initializeEmpty", true); + File databaseUpdateFile = getDatabaseUpdateFile(); - if (globalStatsInterval != -1) { - startGlobalStats(session, globalStatsInterval); + properties.put("hibernate.show_sql", config.getBoolean("showSql", false)); + properties.put("hibernate.format_sql", config.getBoolean("formatSql", true)); + + Connection connection = getConnection(); + try { + prepareOperationalInfo(connection); + + String driverDialect = detectDialect(connection); + if (driverDialect != null) { + properties.put("hibernate.dialect", driverDialect); + } + + migration(migrationStrategy, initializeEmpty, schema, databaseUpdateFile, connection, session); + + int globalStatsInterval = config.getInt("globalStatsInterval", -1); + if (globalStatsInterval != -1) { + properties.put("hibernate.generate_statistics", true); + } + + logger.trace("Creating EntityManagerFactory"); + logger.tracev("***** create EMF jtaEnabled {0} ", jtaEnabled); + if (jtaEnabled) { + properties.put(org.hibernate.cfg.AvailableSettings.JTA_PLATFORM, new AbstractJtaPlatform() { + @Override + protected TransactionManager locateTransactionManager() { + return jtaLookup.getTransactionManager(); + } + + @Override + protected UserTransaction locateUserTransaction() { + return null; + } + }); + } + emf = JpaUtils.createEntityManagerFactory(session, unitName, properties, getClass().getClassLoader(), jtaEnabled); + logger.trace("EntityManagerFactory created"); + + if (globalStatsInterval != -1) { + startGlobalStats(session, globalStatsInterval); + } + } finally { + // Close after creating EntityManagerFactory to prevent in-mem databases from closing + if (connection != null) { + try { + connection.close(); + } catch (SQLException e) { + logger.warn("Can't close connection", e); + } + } } - } finally { - // Close after creating EntityManagerFactory to prevent in-mem databases from closing - if (connection != null) { - try { - connection.close(); - } catch (SQLException e) { - logger.warn("Can't close connection", e); - } - } - } + }); } } } @@ -182,19 +233,19 @@ public class DefaultJpaConnectionProviderFactory implements JpaConnectionProvide } protected void prepareOperationalInfo(Connection connection) { - try { - operationalInfo = new LinkedHashMap<>(); - DatabaseMetaData md = connection.getMetaData(); - operationalInfo.put("databaseUrl",md.getURL()); - operationalInfo.put("databaseUser", md.getUserName()); - operationalInfo.put("databaseProduct", md.getDatabaseProductName() + " " + md.getDatabaseProductVersion()); - operationalInfo.put("databaseDriver", md.getDriverName() + " " + md.getDriverVersion()); + try { + operationalInfo = new LinkedHashMap<>(); + DatabaseMetaData md = connection.getMetaData(); + operationalInfo.put("databaseUrl", md.getURL()); + operationalInfo.put("databaseUser", md.getUserName()); + operationalInfo.put("databaseProduct", md.getDatabaseProductName() + " " + md.getDatabaseProductVersion()); + operationalInfo.put("databaseDriver", md.getDriverName() + " " + md.getDriverVersion()); logger.debugf("Database info: %s", operationalInfo.toString()); - } catch (SQLException e) { - logger.warn("Unable to prepare operational info due database exception: " + e.getMessage()); - } - } + } catch (SQLException e) { + logger.warn("Unable to prepare operational info due database exception: " + e.getMessage()); + } + } protected String detectDialect(Connection connection) { @@ -334,11 +385,11 @@ public class DefaultJpaConnectionProviderFactory implements JpaConnectionProvide public String getSchema() { return config.get("schema"); } - + @Override - public Map getOperationalInfo() { - return operationalInfo; - } + public Map getOperationalInfo() { + return operationalInfo; + } private MigrationStrategy getMigrationStrategy() { String migrationStrategy = config.get("migrationStrategy"); diff --git a/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/lock/LiquibaseDBLockProvider.java b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/lock/LiquibaseDBLockProvider.java index 7c48499798..d080542032 100644 --- a/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/lock/LiquibaseDBLockProvider.java +++ b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/lock/LiquibaseDBLockProvider.java @@ -23,14 +23,13 @@ import java.sql.SQLException; import liquibase.Liquibase; import liquibase.exception.DatabaseException; import liquibase.exception.LiquibaseException; -import liquibase.exception.LockException; -import liquibase.lockservice.LockService; import org.jboss.logging.Logger; import org.keycloak.connections.jpa.JpaConnectionProvider; import org.keycloak.connections.jpa.JpaConnectionProviderFactory; import org.keycloak.connections.jpa.updater.liquibase.conn.LiquibaseConnectionProvider; import org.keycloak.models.KeycloakSession; import org.keycloak.models.dblock.DBLockProvider; +import org.keycloak.models.utils.KeycloakModelUtils; /** * @author Marek Posolda @@ -57,6 +56,7 @@ public class LiquibaseDBLockProvider implements DBLockProvider { this.session = session; } + private void lazyInit() { if (!initialized) { LiquibaseConnectionProvider liquibaseProvider = session.getProvider(LiquibaseConnectionProvider.class); @@ -92,35 +92,41 @@ public class LiquibaseDBLockProvider implements DBLockProvider { @Override public void waitForLock() { - lazyInit(); + KeycloakModelUtils.suspendJtaTransaction(session.getKeycloakSessionFactory(), () -> { - while (maxAttempts > 0) { - try { - lockService.waitForLock(); - factory.setHasLock(true); - this.maxAttempts = DEFAULT_MAX_ATTEMPTS; - return; - } catch (LockRetryException le) { - // Indicates we should try to acquire lock again in different transaction - safeRollbackConnection(); - restart(); - maxAttempts--; - } catch (RuntimeException re) { - safeRollbackConnection(); - safeCloseConnection(); - throw re; + lazyInit(); + + while (maxAttempts > 0) { + try { + lockService.waitForLock(); + factory.setHasLock(true); + this.maxAttempts = DEFAULT_MAX_ATTEMPTS; + return; + } catch (LockRetryException le) { + // Indicates we should try to acquire lock again in different transaction + safeRollbackConnection(); + restart(); + maxAttempts--; + } catch (RuntimeException re) { + safeRollbackConnection(); + safeCloseConnection(); + throw re; + } } - } + }); + } @Override public void releaseLock() { - lazyInit(); + KeycloakModelUtils.suspendJtaTransaction(session.getKeycloakSessionFactory(), () -> { + lazyInit(); - lockService.releaseLock(); - lockService.reset(); - factory.setHasLock(false); + lockService.releaseLock(); + lockService.reset(); + factory.setHasLock(false); + }); } @Override @@ -136,21 +142,25 @@ public class LiquibaseDBLockProvider implements DBLockProvider { @Override public void destroyLockInfo() { - lazyInit(); + KeycloakModelUtils.suspendJtaTransaction(session.getKeycloakSessionFactory(), () -> { + lazyInit(); - try { - this.lockService.destroy(); - dbConnection.commit(); - logger.debug("Destroyed lock table"); - } catch (DatabaseException | SQLException de) { - logger.error("Failed to destroy lock table"); - safeRollbackConnection(); - } + try { + this.lockService.destroy(); + dbConnection.commit(); + logger.debug("Destroyed lock table"); + } catch (DatabaseException | SQLException de) { + logger.error("Failed to destroy lock table"); + safeRollbackConnection(); + } + }); } @Override public void close() { - safeCloseConnection(); + KeycloakModelUtils.suspendJtaTransaction(session.getKeycloakSessionFactory(), () -> { + safeCloseConnection(); + }); } private void safeRollbackConnection() { diff --git a/model/jpa/src/main/java/org/keycloak/connections/jpa/util/JpaUtils.java b/model/jpa/src/main/java/org/keycloak/connections/jpa/util/JpaUtils.java index 5ac7d2f6a9..0385f6aa70 100644 --- a/model/jpa/src/main/java/org/keycloak/connections/jpa/util/JpaUtils.java +++ b/model/jpa/src/main/java/org/keycloak/connections/jpa/util/JpaUtils.java @@ -21,6 +21,8 @@ import org.hibernate.boot.registry.classloading.internal.ClassLoaderServiceImpl; import org.hibernate.jpa.boot.internal.ParsedPersistenceXmlDescriptor; import org.hibernate.jpa.boot.internal.PersistenceXmlParser; import org.hibernate.jpa.boot.spi.Bootstrap; +import org.jboss.logging.Logger; +import org.keycloak.connections.jpa.DefaultJpaConnectionProviderFactory; import org.keycloak.connections.jpa.entityprovider.JpaEntityProvider; import org.keycloak.connections.jpa.entityprovider.ProxyClassLoader; import org.keycloak.models.KeycloakSession; @@ -46,8 +48,9 @@ public class JpaUtils { return (schema==null) ? tableName : schema + "." + tableName; } - public static EntityManagerFactory createEntityManagerFactory(KeycloakSession session, String unitName, Map properties, ClassLoader classLoader) { - PersistenceXmlParser parser = new PersistenceXmlParser(new ClassLoaderServiceImpl(classLoader), PersistenceUnitTransactionType.RESOURCE_LOCAL); + public static EntityManagerFactory createEntityManagerFactory(KeycloakSession session, String unitName, Map properties, ClassLoader classLoader, boolean jta) { + PersistenceUnitTransactionType txType = jta ? PersistenceUnitTransactionType.JTA : PersistenceUnitTransactionType.RESOURCE_LOCAL; + PersistenceXmlParser parser = new PersistenceXmlParser(new ClassLoaderServiceImpl(classLoader), txType); List persistenceUnits = parser.doResolve(properties); for (ParsedPersistenceXmlDescriptor persistenceUnit : persistenceUnits) { if (persistenceUnit.getName().equals(unitName)) { @@ -58,6 +61,7 @@ public class JpaUtils { } // Now build the entity manager factory, supplying a proxy classloader, so Hibernate will be able // to find and load the extra provided entities. Set the provided classloader as parent classloader. + persistenceUnit.setTransactionType(txType); return Bootstrap.getEntityManagerFactoryBuilder(persistenceUnit, properties, new ProxyClassLoader(providedEntities, classLoader)).build(); } diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserCredentialStore.java b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserCredentialStore.java new file mode 100644 index 0000000000..d3539fe8d7 --- /dev/null +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserCredentialStore.java @@ -0,0 +1,220 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.keycloak.models.jpa; + +import org.keycloak.common.util.MultivaluedHashMap; +import org.keycloak.credential.CredentialModel; +import org.keycloak.credential.UserCredentialStore; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.RealmModel; +import org.keycloak.models.UserModel; +import org.keycloak.models.jpa.entities.CredentialAttributeEntity; +import org.keycloak.models.jpa.entities.CredentialEntity; +import org.keycloak.models.jpa.entities.UserEntity; +import org.keycloak.models.utils.KeycloakModelUtils; + +import javax.persistence.EntityManager; +import javax.persistence.TypedQuery; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class JpaUserCredentialStore implements UserCredentialStore { + + private final KeycloakSession session; + protected final EntityManager em; + + public JpaUserCredentialStore(KeycloakSession session, EntityManager em) { + this.session = session; + this.em = em; + } + + @Override + public void updateCredential(RealmModel realm, UserModel user, CredentialModel cred) { + CredentialEntity entity = em.find(CredentialEntity.class, cred.getId()); + if (entity == null) return; + entity.setAlgorithm(cred.getAlgorithm()); + entity.setCounter(cred.getCounter()); + entity.setCreatedDate(cred.getCreatedDate()); + entity.setDevice(cred.getDevice()); + entity.setDigits(cred.getDigits()); + entity.setHashIterations(cred.getHashIterations()); + entity.setPeriod(cred.getPeriod()); + entity.setSalt(cred.getSalt()); + entity.setType(cred.getType()); + entity.setValue(cred.getValue()); + if (entity.getCredentialAttributes().isEmpty() && (cred.getConfig() == null || cred.getConfig().isEmpty())) { + + } else { + MultivaluedHashMap attrs = cred.getConfig(); + MultivaluedHashMap config = cred.getConfig(); + if (config == null) config = new MultivaluedHashMap<>(); + + Iterator it = entity.getCredentialAttributes().iterator(); + while (it.hasNext()) { + CredentialAttributeEntity attr = it.next(); + List values = config.getList(attr.getName()); + if (values == null || !values.contains(attr.getValue())) { + em.remove(attr); + it.remove(); + } else { + attrs.add(attr.getName(), attr.getValue()); + } + + } + for (String key : config.keySet()) { + List values = config.getList(key); + List attrValues = attrs.getList(key); + for (String val : values) { + if (attrValues == null || !attrValues.contains(val)) { + CredentialAttributeEntity attr = new CredentialAttributeEntity(); + attr.setId(KeycloakModelUtils.generateId()); + attr.setValue(val); + attr.setName(key); + attr.setCredential(entity); + em.persist(attr); + entity.getCredentialAttributes().add(attr); + } + } + } + + } + + } + + @Override + public CredentialModel createCredential(RealmModel realm, UserModel user, CredentialModel cred) { + CredentialEntity entity = new CredentialEntity(); + String id = cred.getId() == null ? KeycloakModelUtils.generateId() : cred.getId(); + entity.setId(id); + entity.setAlgorithm(cred.getAlgorithm()); + entity.setCounter(cred.getCounter()); + entity.setCreatedDate(cred.getCreatedDate()); + entity.setDevice(cred.getDevice()); + entity.setDigits(cred.getDigits()); + entity.setHashIterations(cred.getHashIterations()); + entity.setPeriod(cred.getPeriod()); + entity.setSalt(cred.getSalt()); + entity.setType(cred.getType()); + entity.setValue(cred.getValue()); + UserEntity userRef = em.getReference(UserEntity.class, user.getId()); + entity.setUser(userRef); + em.persist(entity); + MultivaluedHashMap config = cred.getConfig(); + if (config != null || !config.isEmpty()) { + + for (String key : config.keySet()) { + List values = config.getList(key); + for (String val : values) { + CredentialAttributeEntity attr = new CredentialAttributeEntity(); + attr.setId(KeycloakModelUtils.generateId()); + attr.setValue(val); + attr.setName(key); + attr.setCredential(entity); + em.persist(attr); + entity.getCredentialAttributes().add(attr); + } + } + + } + return toModel(entity); + } + + @Override + public boolean removeStoredCredential(RealmModel realm, UserModel user, String id) { + CredentialEntity entity = em.find(CredentialEntity.class, id); + if (entity == null) return false; + em.remove(entity); + return true; + } + + @Override + public CredentialModel getStoredCredentialById(RealmModel realm, UserModel user, String id) { + CredentialEntity entity = em.find(CredentialEntity.class, id); + if (entity == null) return null; + CredentialModel model = toModel(entity); + return model; + } + + protected CredentialModel toModel(CredentialEntity entity) { + CredentialModel model = new CredentialModel(); + model.setId(entity.getId()); + model.setType(entity.getType()); + model.setValue(entity.getValue()); + model.setAlgorithm(entity.getAlgorithm()); + model.setSalt(entity.getSalt()); + model.setPeriod(entity.getPeriod()); + model.setCounter(entity.getCounter()); + model.setCreatedDate(entity.getCreatedDate()); + model.setDevice(entity.getDevice()); + model.setDigits(entity.getDigits()); + MultivaluedHashMap config = new MultivaluedHashMap<>(); + model.setConfig(config); + for (CredentialAttributeEntity attr : entity.getCredentialAttributes()) { + config.add(attr.getName(), attr.getValue()); + } + return model; + } + + @Override + public List getStoredCredentials(RealmModel realm, UserModel user) { + UserEntity userEntity = em.getReference(UserEntity.class, user.getId()); + TypedQuery query = em.createNamedQuery("credentialByUser", CredentialEntity.class) + .setParameter("user", userEntity); + List results = query.getResultList(); + List rtn = new LinkedList<>(); + for (CredentialEntity entity : results) { + rtn.add(toModel(entity)); + } + return rtn; + } + + @Override + public List getStoredCredentialsByType(RealmModel realm, UserModel user, String type) { + UserEntity userEntity = em.getReference(UserEntity.class, user.getId()); + TypedQuery query = em.createNamedQuery("credentialByUserAndType", CredentialEntity.class) + .setParameter("type", type) + .setParameter("user", userEntity); + List results = query.getResultList(); + List rtn = new LinkedList<>(); + for (CredentialEntity entity : results) { + rtn.add(toModel(entity)); + } + return rtn; + } + + @Override + public CredentialModel getStoredCredentialByNameAndType(RealmModel realm, UserModel user, String name, String type) { + UserEntity userEntity = em.getReference(UserEntity.class, user.getId()); + TypedQuery query = em.createNamedQuery("credentialByNameAndType", CredentialEntity.class) + .setParameter("type", type) + .setParameter("device", name) + .setParameter("user", userEntity); + List results = query.getResultList(); + if (results.isEmpty()) return null; + return toModel(results.get(0)); + } + + @Override + public void close() { + + } +} diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserCredentialStoreFactory.java b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserCredentialStoreFactory.java new file mode 100755 index 0000000000..550299ba8b --- /dev/null +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserCredentialStoreFactory.java @@ -0,0 +1,59 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.keycloak.models.jpa; + +import org.keycloak.Config; +import org.keycloak.connections.jpa.JpaConnectionProvider; +import org.keycloak.credential.UserCredentialStore; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.KeycloakSessionFactory; +import org.keycloak.provider.ProviderFactory; + +import javax.persistence.EntityManager; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class JpaUserCredentialStoreFactory implements ProviderFactory { + + @Override + public void init(Config.Scope config) { + } + + @Override + public void postInit(KeycloakSessionFactory factory) { + + } + + @Override + public String getId() { + return "jpa"; + } + + @Override + public UserCredentialStore create(KeycloakSession session) { + EntityManager em = session.getProvider(JpaConnectionProvider.class).getEntityManager(); + return new JpaUserCredentialStore(session, em); + } + + @Override + public void close() { + } + +} diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java index 0d938d61bb..d44c6fe171 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java @@ -17,7 +17,11 @@ package org.keycloak.models.jpa; +import org.keycloak.common.util.MultivaluedHashMap; import org.keycloak.component.ComponentModel; +import org.keycloak.credential.CredentialInput; +import org.keycloak.credential.CredentialModel; +import org.keycloak.credential.UserCredentialStore; import org.keycloak.models.ClientModel; import org.keycloak.models.CredentialValidationOutput; import org.keycloak.models.FederatedIdentityModel; @@ -34,6 +38,8 @@ import org.keycloak.models.UserCredentialModel; import org.keycloak.models.UserFederationProviderModel; import org.keycloak.models.UserModel; import org.keycloak.models.UserProvider; +import org.keycloak.models.jpa.entities.CredentialAttributeEntity; +import org.keycloak.models.jpa.entities.CredentialEntity; import org.keycloak.models.jpa.entities.FederatedIdentityEntity; import org.keycloak.models.jpa.entities.UserAttributeEntity; import org.keycloak.models.jpa.entities.UserConsentEntity; @@ -48,7 +54,9 @@ import javax.persistence.EntityManager; import javax.persistence.TypedQuery; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.HashSet; +import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -58,7 +66,7 @@ import java.util.Set; * @author Bill Burke * @version $Revision: 1 $ */ -public class JpaUserProvider implements UserProvider { +public class JpaUserProvider implements UserProvider, UserCredentialStore { private static final String EMAIL = "email"; private static final String USERNAME = "username"; @@ -367,6 +375,8 @@ public class JpaUserProvider implements UserProvider { .setParameter("realmId", realm.getId()).executeUpdate(); num = em.createNamedQuery("deleteFederatedIdentityByRealm") .setParameter("realmId", realm.getId()).executeUpdate(); + num = em.createNamedQuery("deleteCredentialAttributeByRealm") + .setParameter("realmId", realm.getId()).executeUpdate(); num = em.createNamedQuery("deleteCredentialsByRealm") .setParameter("realmId", realm.getId()).executeUpdate(); num = em.createNamedQuery("deleteUserAttributesByRealm") @@ -391,6 +401,10 @@ public class JpaUserProvider implements UserProvider { .setParameter("realmId", realm.getId()) .setParameter("link", link.getId()) .executeUpdate(); + num = em.createNamedQuery("deleteCredentialAttributeByRealmAndLink") + .setParameter("realmId", realm.getId()) + .setParameter("link", link.getId()) + .executeUpdate(); num = em.createNamedQuery("deleteCredentialsByRealmAndLink") .setParameter("realmId", realm.getId()) .setParameter("link", link.getId()) @@ -716,4 +730,174 @@ public class JpaUserProvider implements UserProvider { public void preRemove(RealmModel realm, ComponentModel component) { } + + @Override + public void updateCredential(RealmModel realm, UserModel user, CredentialModel cred) { + CredentialEntity entity = em.find(CredentialEntity.class, cred.getId()); + if (entity == null) return; + entity.setAlgorithm(cred.getAlgorithm()); + entity.setCounter(cred.getCounter()); + entity.setCreatedDate(cred.getCreatedDate()); + entity.setDevice(cred.getDevice()); + entity.setDigits(cred.getDigits()); + entity.setHashIterations(cred.getHashIterations()); + entity.setPeriod(cred.getPeriod()); + entity.setSalt(cred.getSalt()); + entity.setType(cred.getType()); + entity.setValue(cred.getValue()); + if (entity.getCredentialAttributes().isEmpty() && (cred.getConfig() == null || cred.getConfig().isEmpty())) { + + } else { + MultivaluedHashMap attrs = cred.getConfig(); + MultivaluedHashMap config = cred.getConfig(); + if (config == null) config = new MultivaluedHashMap<>(); + + Iterator it = entity.getCredentialAttributes().iterator(); + while (it.hasNext()) { + CredentialAttributeEntity attr = it.next(); + List values = config.getList(attr.getName()); + if (values == null || !values.contains(attr.getValue())) { + em.remove(attr); + it.remove(); + } else { + attrs.add(attr.getName(), attr.getValue()); + } + + } + for (String key : config.keySet()) { + List values = config.getList(key); + List attrValues = attrs.getList(key); + for (String val : values) { + if (attrValues == null || !attrValues.contains(val)) { + CredentialAttributeEntity attr = new CredentialAttributeEntity(); + attr.setId(KeycloakModelUtils.generateId()); + attr.setValue(val); + attr.setName(key); + attr.setCredential(entity); + em.persist(attr); + entity.getCredentialAttributes().add(attr); + } + } + } + + } + + } + + @Override + public CredentialModel createCredential(RealmModel realm, UserModel user, CredentialModel cred) { + CredentialEntity entity = new CredentialEntity(); + String id = cred.getId() == null ? KeycloakModelUtils.generateId() : cred.getId(); + entity.setId(id); + entity.setAlgorithm(cred.getAlgorithm()); + entity.setCounter(cred.getCounter()); + entity.setCreatedDate(cred.getCreatedDate()); + entity.setDevice(cred.getDevice()); + entity.setDigits(cred.getDigits()); + entity.setHashIterations(cred.getHashIterations()); + entity.setPeriod(cred.getPeriod()); + entity.setSalt(cred.getSalt()); + entity.setType(cred.getType()); + entity.setValue(cred.getValue()); + UserEntity userRef = em.getReference(UserEntity.class, user.getId()); + entity.setUser(userRef); + em.persist(entity); + MultivaluedHashMap config = cred.getConfig(); + if (config != null || !config.isEmpty()) { + + for (String key : config.keySet()) { + List values = config.getList(key); + for (String val : values) { + CredentialAttributeEntity attr = new CredentialAttributeEntity(); + attr.setId(KeycloakModelUtils.generateId()); + attr.setValue(val); + attr.setName(key); + attr.setCredential(entity); + em.persist(attr); + entity.getCredentialAttributes().add(attr); + } + } + + } + return toModel(entity); + } + + @Override + public boolean removeStoredCredential(RealmModel realm, UserModel user, String id) { + CredentialEntity entity = em.find(CredentialEntity.class, id); + if (entity == null) return false; + em.remove(entity); + return true; + } + + @Override + public CredentialModel getStoredCredentialById(RealmModel realm, UserModel user, String id) { + CredentialEntity entity = em.find(CredentialEntity.class, id); + if (entity == null) return null; + CredentialModel model = toModel(entity); + return model; + } + + protected CredentialModel toModel(CredentialEntity entity) { + CredentialModel model = new CredentialModel(); + model.setId(entity.getId()); + model.setType(entity.getType()); + model.setValue(entity.getValue()); + model.setAlgorithm(entity.getAlgorithm()); + model.setSalt(entity.getSalt()); + model.setPeriod(entity.getPeriod()); + model.setCounter(entity.getCounter()); + model.setCreatedDate(entity.getCreatedDate()); + model.setDevice(entity.getDevice()); + model.setDigits(entity.getDigits()); + MultivaluedHashMap config = new MultivaluedHashMap<>(); + model.setConfig(config); + for (CredentialAttributeEntity attr : entity.getCredentialAttributes()) { + config.add(attr.getName(), attr.getValue()); + } + return model; + } + + @Override + public List getStoredCredentials(RealmModel realm, UserModel user) { + UserEntity userEntity = em.getReference(UserEntity.class, user.getId()); + TypedQuery query = em.createNamedQuery("credentialByUser", CredentialEntity.class) + .setParameter("user", userEntity); + List results = query.getResultList(); + List rtn = new LinkedList<>(); + for (CredentialEntity entity : results) { + rtn.add(toModel(entity)); + } + return rtn; + } + + @Override + public List getStoredCredentialsByType(RealmModel realm, UserModel user, String type) { + UserEntity userEntity = em.getReference(UserEntity.class, user.getId()); + TypedQuery query = em.createNamedQuery("credentialByUserAndType", CredentialEntity.class) + .setParameter("type", type) + .setParameter("user", userEntity); + List results = query.getResultList(); + List rtn = new LinkedList<>(); + for (CredentialEntity entity : results) { + rtn.add(toModel(entity)); + } + return rtn; + } + + @Override + public CredentialModel getStoredCredentialByNameAndType(RealmModel realm, UserModel user, String name, String type) { + UserEntity userEntity = em.getReference(UserEntity.class, user.getId()); + TypedQuery query = em.createNamedQuery("credentialByNameAndType", CredentialEntity.class) + .setParameter("type", type) + .setParameter("device", name) + .setParameter("user", userEntity); + List results = query.getResultList(); + if (results.isEmpty()) return null; + return toModel(results.get(0)); + } + + + + } diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java index 46df41a455..a5747c6285 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java @@ -182,6 +182,7 @@ public class RealmAdapter implements RealmModel, JpaModel { em.flush(); } + @Override public void setAttribute(String name, String value) { for (RealmAttributeEntity attr : realm.getAttributes()) { if (attr.getName().equals(name)) { @@ -197,18 +198,22 @@ public class RealmAdapter implements RealmModel, JpaModel { realm.getAttributes().add(attr); } + @Override public void setAttribute(String name, Boolean value) { setAttribute(name, value.toString()); } + @Override public void setAttribute(String name, Integer value) { setAttribute(name, value.toString()); } + @Override public void setAttribute(String name, Long value) { setAttribute(name, value.toString()); } + @Override public void removeAttribute(String name) { Iterator it = realm.getAttributes().iterator(); while (it.hasNext()) { @@ -220,6 +225,7 @@ public class RealmAdapter implements RealmModel, JpaModel { } } + @Override public String getAttribute(String name) { for (RealmAttributeEntity attr : realm.getAttributes()) { if (attr.getName().equals(name)) { @@ -229,24 +235,28 @@ public class RealmAdapter implements RealmModel, JpaModel { return null; } + @Override public Integer getAttribute(String name, Integer defaultValue) { String v = getAttribute(name); return v != null ? Integer.parseInt(v) : defaultValue; } + @Override public Long getAttribute(String name, Long defaultValue) { String v = getAttribute(name); return v != null ? Long.parseLong(v) : defaultValue; } + @Override public Boolean getAttribute(String name, Boolean defaultValue) { String v = getAttribute(name); return v != null ? Boolean.parseBoolean(v) : defaultValue; } + @Override public Map getAttributes() { // should always return a copy Map result = new HashMap(); @@ -255,6 +265,7 @@ public class RealmAdapter implements RealmModel, JpaModel { } return result; } + @Override public boolean isBruteForceProtected() { return getAttribute("bruteForceProtected", false); diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java index 2ea12fd839..49bcac633b 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java @@ -125,29 +125,32 @@ public class UserAdapter implements UserModel, JpaModel { @Override public void setSingleAttribute(String name, String value) { - boolean found = false; + String firstExistingAttrId = null; List toRemove = new ArrayList<>(); for (UserAttributeEntity attr : user.getAttributes()) { if (attr.getName().equals(name)) { - if (!found) { + if (firstExistingAttrId == null) { attr.setValue(value); - found = true; + firstExistingAttrId = attr.getId(); } else { toRemove.add(attr); } } } - for (UserAttributeEntity attr : toRemove) { - em.remove(attr); - user.getAttributes().remove(attr); - } + if (firstExistingAttrId != null) { + // Remove attributes through HQL to avoid StaleUpdateException + Query query = em.createNamedQuery("deleteUserAttributesOtherThan"); + query.setParameter("attrId", firstExistingAttrId); + query.setParameter("userId", user.getId()); + int numUpdated = query.executeUpdate(); - if (found) { - return; - } + // Remove attribute from local entity + user.getAttributes().removeAll(toRemove); + } else { - persistAttributeValue(name, value); + persistAttributeValue(name, value); + } } @Override @@ -178,6 +181,15 @@ public class UserAdapter implements UserModel, JpaModel { query.setParameter("name", name); query.setParameter("userId", user.getId()); int numUpdated = query.executeUpdate(); + + // KEYCLOAK-3494 : Also remove attributes from local user entity + List toRemove = new ArrayList<>(); + for (UserAttributeEntity attr : user.getAttributes()) { + if (attr.getName().equals(name)) { + toRemove.add(attr); + } + } + user.getAttributes().removeAll(toRemove); } @Override diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/CredentialAttributeEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/CredentialAttributeEntity.java new file mode 100755 index 0000000000..f4ceb947ee --- /dev/null +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/CredentialAttributeEntity.java @@ -0,0 +1,111 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.keycloak.models.jpa.entities; + +import javax.persistence.Access; +import javax.persistence.AccessType; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.NamedQueries; +import javax.persistence.NamedQuery; +import javax.persistence.Table; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +@NamedQueries({ + @NamedQuery(name="getCredentialAttribute", query="select attr from CredentialAttributeEntity attr where attr.credential = :credential"), + @NamedQuery(name="deleteCredentialAttributeByCredential", query="delete from CredentialAttributeEntity attr where attr.credential = :credential"), + @NamedQuery(name="deleteCredentialAttributeByRealm", query="delete from CredentialAttributeEntity attr where attr.credential IN (select cred from CredentialEntity cred where cred.user IN (select u from UserEntity u where u.realmId=:realmId))"), + @NamedQuery(name="deleteCredentialAttributeByRealmAndLink", query="delete from CredentialAttributeEntity attr where attr.credential IN (select cred from CredentialEntity cred where cred.user IN (select u from UserEntity u where u.realmId=:realmId and u.federationLink=:link))"), + @NamedQuery(name="deleteCredentialAttributeByUser", query="delete from CredentialAttributeEntity attr where attr.credential IN (select cred from CredentialEntity cred where cred.user = :user)"), +}) +@Table(name="CREDENTIAL_ATTRIBUTE") +@Entity +public class CredentialAttributeEntity { + + @Id + @Column(name="ID", length = 36) + @Access(AccessType.PROPERTY) // we do this because relationships often fetch id, but not entity. This avoids an extra SQL + protected String id; + + @ManyToOne(fetch= FetchType.LAZY) + @JoinColumn(name = "CREDENTIAL_ID") + protected CredentialEntity credential; + + @Column(name = "NAME") + protected String name; + @Column(name = "VALUE") + protected String value; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public CredentialEntity getCredential() { + return credential; + } + + public void setCredential(CredentialEntity credential) { + this.credential = credential; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null) return false; + if (!(o instanceof CredentialAttributeEntity)) return false; + + CredentialAttributeEntity that = (CredentialAttributeEntity) o; + + if (!id.equals(that.getId())) return false; + + return true; + } + + @Override + public int hashCode() { + return id.hashCode(); + } + +} diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/CredentialEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/CredentialEntity.java index ceb284cb06..4c8f0b3e83 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/CredentialEntity.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/CredentialEntity.java @@ -19,6 +19,7 @@ package org.keycloak.models.jpa.entities; import javax.persistence.Access; import javax.persistence.AccessType; +import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.FetchType; @@ -27,14 +28,19 @@ import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; import javax.persistence.NamedQueries; import javax.persistence.NamedQuery; +import javax.persistence.OneToMany; import javax.persistence.Table; +import java.util.ArrayList; +import java.util.Collection; /** * @author Bill Burke * @version $Revision: 1 $ */ @NamedQueries({ + @NamedQuery(name="credentialByUser", query="select cred from CredentialEntity cred where cred.user = :user"), @NamedQuery(name="credentialByUserAndType", query="select cred from CredentialEntity cred where cred.user = :user and cred.type = :type"), + @NamedQuery(name="credentialByNameAndType", query="select cred from CredentialEntity cred where cred.user = :user and cred.type = :type and cred.device = :device"), @NamedQuery(name="deleteCredentialsByRealm", query="delete from CredentialEntity cred where cred.user IN (select u from UserEntity u where u.realmId=:realmId)"), @NamedQuery(name="deleteCredentialsByRealmAndLink", query="delete from CredentialEntity cred where cred.user IN (select u from UserEntity u where u.realmId=:realmId and u.federationLink=:link)") @@ -74,6 +80,8 @@ public class CredentialEntity { @Column(name="PERIOD") protected int period; + @OneToMany(cascade = CascadeType.REMOVE, fetch = FetchType.EAGER, orphanRemoval = true, mappedBy="credential") + protected Collection credentialAttributes = new ArrayList<>(); public String getId() { return id; @@ -171,6 +179,14 @@ public class CredentialEntity { this.period = period; } + public Collection getCredentialAttributes() { + return credentialAttributes; + } + + public void setCredentialAttributes(Collection credentialAttributes) { + this.credentialAttributes = credentialAttributes; + } + @Override public boolean equals(Object o) { if (this == o) return true; diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserAttributeEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserAttributeEntity.java index 2b5d35cc63..16b155b75b 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserAttributeEntity.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserAttributeEntity.java @@ -43,6 +43,7 @@ import java.util.Set; @NamedQuery(name="getAttributesByNameAndValue", query="select attr from UserAttributeEntity attr where attr.name = :name and attr.value = :value"), @NamedQuery(name="deleteUserAttributesByRealm", query="delete from UserAttributeEntity attr where attr.user IN (select u from UserEntity u where u.realmId=:realmId)"), @NamedQuery(name="deleteUserAttributesByNameAndUser", query="delete from UserAttributeEntity attr where attr.user.id = :userId and attr.name = :name"), + @NamedQuery(name="deleteUserAttributesOtherThan", query="delete from UserAttributeEntity attr where attr.user.id = :userId and attr.id <> :attrId"), @NamedQuery(name="deleteUserAttributesByRealmAndLink", query="delete from UserAttributeEntity attr where attr.user IN (select u from UserEntity u where u.realmId=:realmId and u.federationLink=:link)") }) @Table(name="USER_ATTRIBUTE") diff --git a/model/jpa/src/main/java/org/keycloak/storage/jpa/JpaFederatedUserCredentialStore.java b/model/jpa/src/main/java/org/keycloak/storage/jpa/JpaFederatedUserCredentialStore.java new file mode 100644 index 0000000000..13fa03218f --- /dev/null +++ b/model/jpa/src/main/java/org/keycloak/storage/jpa/JpaFederatedUserCredentialStore.java @@ -0,0 +1,218 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.keycloak.storage.jpa; + +import org.keycloak.common.util.MultivaluedHashMap; +import org.keycloak.credential.CredentialModel; +import org.keycloak.credential.UserCredentialStore; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.RealmModel; +import org.keycloak.models.UserModel; +import org.keycloak.models.utils.KeycloakModelUtils; +import org.keycloak.storage.StorageId; +import org.keycloak.storage.jpa.entity.FederatedUserCredentialAttributeEntity; +import org.keycloak.storage.jpa.entity.FederatedUserCredentialEntity; + +import javax.persistence.EntityManager; +import javax.persistence.TypedQuery; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class JpaFederatedUserCredentialStore implements UserCredentialStore { + + private final KeycloakSession session; + protected final EntityManager em; + + public JpaFederatedUserCredentialStore(KeycloakSession session, EntityManager em) { + this.session = session; + this.em = em; + } + + @Override + public void updateCredential(RealmModel realm, UserModel user, CredentialModel cred) { + FederatedUserCredentialEntity entity = em.find(FederatedUserCredentialEntity.class, cred.getId()); + if (entity == null) return; + entity.setAlgorithm(cred.getAlgorithm()); + entity.setCounter(cred.getCounter()); + entity.setCreatedDate(cred.getCreatedDate()); + entity.setDevice(cred.getDevice()); + entity.setDigits(cred.getDigits()); + entity.setHashIterations(cred.getHashIterations()); + entity.setPeriod(cred.getPeriod()); + entity.setSalt(cred.getSalt()); + entity.setType(cred.getType()); + entity.setValue(cred.getValue()); + if (entity.getCredentialAttributes().isEmpty() && (cred.getConfig() == null || cred.getConfig().isEmpty())) { + + } else { + MultivaluedHashMap attrs = cred.getConfig(); + MultivaluedHashMap config = cred.getConfig(); + if (config == null) config = new MultivaluedHashMap<>(); + + Iterator it = entity.getCredentialAttributes().iterator(); + while (it.hasNext()) { + FederatedUserCredentialAttributeEntity attr = it.next(); + List values = config.getList(attr.getName()); + if (values == null || !values.contains(attr.getValue())) { + em.remove(attr); + it.remove(); + } else { + attrs.add(attr.getName(), attr.getValue()); + } + + } + for (String key : config.keySet()) { + List values = config.getList(key); + List attrValues = attrs.getList(key); + for (String val : values) { + if (attrValues == null || !attrValues.contains(val)) { + FederatedUserCredentialAttributeEntity attr = new FederatedUserCredentialAttributeEntity(); + attr.setId(KeycloakModelUtils.generateId()); + attr.setValue(val); + attr.setName(key); + attr.setCredential(entity); + em.persist(attr); + entity.getCredentialAttributes().add(attr); + } + } + } + + } + + } + + @Override + public CredentialModel createCredential(RealmModel realm, UserModel user, CredentialModel cred) { + FederatedUserCredentialEntity entity = new FederatedUserCredentialEntity(); + String id = cred.getId() == null ? KeycloakModelUtils.generateId() : cred.getId(); + entity.setId(id); + entity.setAlgorithm(cred.getAlgorithm()); + entity.setCounter(cred.getCounter()); + entity.setCreatedDate(cred.getCreatedDate()); + entity.setDevice(cred.getDevice()); + entity.setDigits(cred.getDigits()); + entity.setHashIterations(cred.getHashIterations()); + entity.setPeriod(cred.getPeriod()); + entity.setSalt(cred.getSalt()); + entity.setType(cred.getType()); + entity.setValue(cred.getValue()); + entity.setUserId(user.getId()); + entity.setRealmId(realm.getId()); + entity.setStorageProviderId(StorageId.resolveProviderId(user)); + em.persist(entity); + MultivaluedHashMap config = cred.getConfig(); + if (config != null || !config.isEmpty()) { + + for (String key : config.keySet()) { + List values = config.getList(key); + for (String val : values) { + FederatedUserCredentialAttributeEntity attr = new FederatedUserCredentialAttributeEntity(); + attr.setId(KeycloakModelUtils.generateId()); + attr.setValue(val); + attr.setName(key); + attr.setCredential(entity); + em.persist(attr); + entity.getCredentialAttributes().add(attr); + } + } + + } + return toModel(entity); + } + + @Override + public boolean removeStoredCredential(RealmModel realm, UserModel user, String id) { + FederatedUserCredentialEntity entity = em.find(FederatedUserCredentialEntity.class, id); + if (entity == null) return false; + em.remove(entity); + return true; + } + + @Override + public CredentialModel getStoredCredentialById(RealmModel realm, UserModel user, String id) { + FederatedUserCredentialEntity entity = em.find(FederatedUserCredentialEntity.class, id); + if (entity == null) return null; + CredentialModel model = toModel(entity); + return model; + } + + protected CredentialModel toModel(FederatedUserCredentialEntity entity) { + CredentialModel model = new CredentialModel(); + model.setId(entity.getId()); + model.setType(entity.getType()); + model.setValue(entity.getValue()); + model.setAlgorithm(entity.getAlgorithm()); + model.setSalt(entity.getSalt()); + model.setPeriod(entity.getPeriod()); + model.setCounter(entity.getCounter()); + model.setCreatedDate(entity.getCreatedDate()); + model.setDevice(entity.getDevice()); + model.setDigits(entity.getDigits()); + MultivaluedHashMap config = new MultivaluedHashMap<>(); + model.setConfig(config); + for (FederatedUserCredentialAttributeEntity attr : entity.getCredentialAttributes()) { + config.add(attr.getName(), attr.getValue()); + } + return model; + } + + @Override + public List getStoredCredentials(RealmModel realm, UserModel user) { + TypedQuery query = em.createNamedQuery("federatedUserCredentialByUser", FederatedUserCredentialEntity.class) + .setParameter("userId", user.getId()); + List results = query.getResultList(); + List rtn = new LinkedList<>(); + for (FederatedUserCredentialEntity entity : results) { + rtn.add(toModel(entity)); + } + return rtn; + } + + @Override + public List getStoredCredentialsByType(RealmModel realm, UserModel user, String type) { + TypedQuery query = em.createNamedQuery("federatedUserCredentialByUserAndType", FederatedUserCredentialEntity.class) + .setParameter("type", type) + .setParameter("userId", user.getId()); + List results = query.getResultList(); + List rtn = new LinkedList<>(); + for (FederatedUserCredentialEntity entity : results) { + rtn.add(toModel(entity)); + } + return rtn; + } + + @Override + public CredentialModel getStoredCredentialByNameAndType(RealmModel realm, UserModel user, String name, String type) { + TypedQuery query = em.createNamedQuery("federatedUserCredentialByNameAndType", FederatedUserCredentialEntity.class) + .setParameter("type", type) + .setParameter("device", name) + .setParameter("userId", user.getId()); + List results = query.getResultList(); + if (results.isEmpty()) return null; + return toModel(results.get(0)); + } + + @Override + public void close() { + + } +} diff --git a/model/jpa/src/main/java/org/keycloak/storage/jpa/JpaUserFederatedStorageProvider.java b/model/jpa/src/main/java/org/keycloak/storage/jpa/JpaUserFederatedStorageProvider.java index f6710dd3d5..1c72da0744 100644 --- a/model/jpa/src/main/java/org/keycloak/storage/jpa/JpaUserFederatedStorageProvider.java +++ b/model/jpa/src/main/java/org/keycloak/storage/jpa/JpaUserFederatedStorageProvider.java @@ -18,6 +18,8 @@ package org.keycloak.storage.jpa; import org.keycloak.common.util.MultivaluedHashMap; import org.keycloak.component.ComponentModel; +import org.keycloak.credential.CredentialModel; +import org.keycloak.credential.UserCredentialStore; import org.keycloak.models.ClientModel; import org.keycloak.models.FederatedIdentityModel; import org.keycloak.models.GroupModel; @@ -49,6 +51,7 @@ import org.keycloak.storage.jpa.entity.FederatedUserAttributeEntity; import org.keycloak.storage.jpa.entity.FederatedUserConsentEntity; import org.keycloak.storage.jpa.entity.FederatedUserConsentProtocolMapperEntity; import org.keycloak.storage.jpa.entity.FederatedUserConsentRoleEntity; +import org.keycloak.storage.jpa.entity.FederatedUserCredentialAttributeEntity; import org.keycloak.storage.jpa.entity.FederatedUserCredentialEntity; import org.keycloak.storage.jpa.entity.FederatedUserGroupMembershipEntity; import org.keycloak.storage.jpa.entity.FederatedUserRequiredActionEntity; @@ -59,6 +62,7 @@ import javax.persistence.TypedQuery; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; +import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Set; @@ -75,7 +79,8 @@ public class JpaUserFederatedStorageProvider implements UserCredentialsFederatedStorage, UserGroupMembershipFederatedStorage, UserRequiredActionsFederatedStorage, - UserRoleMappingsFederatedStorage { + UserRoleMappingsFederatedStorage, + UserCredentialStore { private final KeycloakSession session; protected EntityManager em; @@ -606,6 +611,200 @@ public class JpaUserFederatedStorageProvider implements em.flush(); } + @Override + public boolean removeCredential(RealmModel realm, String id) { + return false; + } + + @Override + public CredentialModel getCredentialById(String id) { + return null; + } + + @Override + public List getCredentials(RealmModel realm) { + return null; + } + + @Override + public List getUserCredentials(RealmModel realm, UserModel user) { + return null; + } + + @Override + public List getCredentialsByType(RealmModel realm, UserModel user, String type) { + return null; + } + + @Override + public CredentialModel getCredentialByNameAndType(RealmModel realm, UserModel user, String name, String type) { + return null; + } + + @Override + public void updateCredential(RealmModel realm, UserModel user, CredentialModel cred) { + FederatedUserCredentialEntity entity = em.find(FederatedUserCredentialEntity.class, cred.getId()); + if (entity == null) return; + entity.setAlgorithm(cred.getAlgorithm()); + entity.setCounter(cred.getCounter()); + entity.setCreatedDate(cred.getCreatedDate()); + entity.setDevice(cred.getDevice()); + entity.setDigits(cred.getDigits()); + entity.setHashIterations(cred.getHashIterations()); + entity.setPeriod(cred.getPeriod()); + entity.setSalt(cred.getSalt()); + entity.setType(cred.getType()); + entity.setValue(cred.getValue()); + if (entity.getCredentialAttributes().isEmpty() && (cred.getConfig() == null || cred.getConfig().isEmpty())) { + + } else { + MultivaluedHashMap attrs = cred.getConfig(); + MultivaluedHashMap config = cred.getConfig(); + if (config == null) config = new MultivaluedHashMap<>(); + + Iterator it = entity.getCredentialAttributes().iterator(); + while (it.hasNext()) { + FederatedUserCredentialAttributeEntity attr = it.next(); + List values = config.getList(attr.getName()); + if (values == null || !values.contains(attr.getValue())) { + em.remove(attr); + it.remove(); + } else { + attrs.add(attr.getName(), attr.getValue()); + } + + } + for (String key : config.keySet()) { + List values = config.getList(key); + List attrValues = attrs.getList(key); + for (String val : values) { + if (attrValues == null || !attrValues.contains(val)) { + FederatedUserCredentialAttributeEntity attr = new FederatedUserCredentialAttributeEntity(); + attr.setId(KeycloakModelUtils.generateId()); + attr.setValue(val); + attr.setName(key); + attr.setCredential(entity); + em.persist(attr); + entity.getCredentialAttributes().add(attr); + } + } + } + + } + + } + + @Override + public CredentialModel createCredential(RealmModel realm, UserModel user, CredentialModel cred) { + FederatedUserCredentialEntity entity = new FederatedUserCredentialEntity(); + String id = cred.getId() == null ? KeycloakModelUtils.generateId() : cred.getId(); + entity.setId(id); + entity.setAlgorithm(cred.getAlgorithm()); + entity.setCounter(cred.getCounter()); + entity.setCreatedDate(cred.getCreatedDate()); + entity.setDevice(cred.getDevice()); + entity.setDigits(cred.getDigits()); + entity.setHashIterations(cred.getHashIterations()); + entity.setPeriod(cred.getPeriod()); + entity.setSalt(cred.getSalt()); + entity.setType(cred.getType()); + entity.setValue(cred.getValue()); + entity.setUserId(user.getId()); + entity.setRealmId(realm.getId()); + entity.setStorageProviderId(StorageId.resolveProviderId(user)); + em.persist(entity); + MultivaluedHashMap config = cred.getConfig(); + if (config != null || !config.isEmpty()) { + + for (String key : config.keySet()) { + List values = config.getList(key); + for (String val : values) { + FederatedUserCredentialAttributeEntity attr = new FederatedUserCredentialAttributeEntity(); + attr.setId(KeycloakModelUtils.generateId()); + attr.setValue(val); + attr.setName(key); + attr.setCredential(entity); + em.persist(attr); + entity.getCredentialAttributes().add(attr); + } + } + + } + return toModel(entity); + } + + @Override + public boolean removeStoredCredential(RealmModel realm, UserModel user, String id) { + FederatedUserCredentialEntity entity = em.find(FederatedUserCredentialEntity.class, id); + if (entity == null) return false; + em.remove(entity); + return true; + } + + @Override + public CredentialModel getStoredCredentialById(RealmModel realm, UserModel user, String id) { + FederatedUserCredentialEntity entity = em.find(FederatedUserCredentialEntity.class, id); + if (entity == null) return null; + CredentialModel model = toModel(entity); + return model; + } + + protected CredentialModel toModel(FederatedUserCredentialEntity entity) { + CredentialModel model = new CredentialModel(); + model.setId(entity.getId()); + model.setType(entity.getType()); + model.setValue(entity.getValue()); + model.setAlgorithm(entity.getAlgorithm()); + model.setSalt(entity.getSalt()); + model.setPeriod(entity.getPeriod()); + model.setCounter(entity.getCounter()); + model.setCreatedDate(entity.getCreatedDate()); + model.setDevice(entity.getDevice()); + model.setDigits(entity.getDigits()); + MultivaluedHashMap config = new MultivaluedHashMap<>(); + model.setConfig(config); + for (FederatedUserCredentialAttributeEntity attr : entity.getCredentialAttributes()) { + config.add(attr.getName(), attr.getValue()); + } + return model; + } + + @Override + public List getStoredCredentials(RealmModel realm, UserModel user) { + TypedQuery query = em.createNamedQuery("federatedUserCredentialByUser", FederatedUserCredentialEntity.class) + .setParameter("userId", user.getId()); + List results = query.getResultList(); + List rtn = new LinkedList<>(); + for (FederatedUserCredentialEntity entity : results) { + rtn.add(toModel(entity)); + } + return rtn; + } + + @Override + public List getStoredCredentialsByType(RealmModel realm, UserModel user, String type) { + TypedQuery query = em.createNamedQuery("federatedUserCredentialByUserAndType", FederatedUserCredentialEntity.class) + .setParameter("type", type) + .setParameter("userId", user.getId()); + List results = query.getResultList(); + List rtn = new LinkedList<>(); + for (FederatedUserCredentialEntity entity : results) { + rtn.add(toModel(entity)); + } + return rtn; + } + + @Override + public CredentialModel getStoredCredentialByNameAndType(RealmModel realm, UserModel user, String name, String type) { + TypedQuery query = em.createNamedQuery("federatedUserCredentialByNameAndType", FederatedUserCredentialEntity.class) + .setParameter("type", type) + .setParameter("device", name) + .setParameter("userId", user.getId()); + List results = query.getResultList(); + if (results.isEmpty()) return null; + return toModel(results.get(0)); + } + @Override public void preRemove(RealmModel realm) { int num = em.createNamedQuery("deleteFederatedUserConsentRolesByRealm") @@ -620,6 +819,8 @@ public class JpaUserFederatedStorageProvider implements .setParameter("realmId", realm.getId()).executeUpdate(); num = em.createNamedQuery("deleteBrokerLinkByRealm") .setParameter("realmId", realm.getId()).executeUpdate(); + num = em.createNamedQuery("deleteFederatedCredentialAttributeByRealm") + .setParameter("realmId", realm.getId()).executeUpdate(); num = em.createNamedQuery("deleteFederatedUserCredentialsByRealm") .setParameter("realmId", realm.getId()).executeUpdate(); num = em.createNamedQuery("deleteUserFederatedAttributesByRealm") @@ -642,6 +843,10 @@ public class JpaUserFederatedStorageProvider implements .setParameter("realmId", realm.getId()) .setParameter("link", link.getId()) .executeUpdate(); + num = em.createNamedQuery("deleteFederatedCredentialAttributeByRealmAndLink") + .setParameter("realmId", realm.getId()) + .setParameter("link", link.getId()) + .executeUpdate(); num = em.createNamedQuery("deleteFederatedUserCredentialsByRealmAndLink") .setParameter("realmId", realm.getId()) .setParameter("link", link.getId()) @@ -699,6 +904,10 @@ public class JpaUserFederatedStorageProvider implements .setParameter("userId", user.getId()) .setParameter("realmId", realm.getId()) .executeUpdate(); + em.createNamedQuery("deleteFederatedCredentialAttributeByUser") + .setParameter("userId", user.getId()) + .setParameter("realmId", realm.getId()) + .executeUpdate(); em.createNamedQuery("deleteFederatedUserCredentialByUser") .setParameter("userId", user.getId()) .setParameter("realmId", realm.getId()) @@ -737,6 +946,9 @@ public class JpaUserFederatedStorageProvider implements em.createNamedQuery("deleteFederatedUserConsentsByStorageProvider") .setParameter("storageProviderId", model.getId()) .executeUpdate(); + em.createNamedQuery("deleteFederatedCredentialAttributeByStorageProvider") + .setParameter("storageProviderId", model.getId()) + .executeUpdate(); em.createNamedQuery("deleteFederatedUserCredentialsByStorageProvider") .setParameter("storageProviderId", model.getId()) .executeUpdate(); diff --git a/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserCredentialAttributeEntity.java b/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserCredentialAttributeEntity.java new file mode 100755 index 0000000000..c6a15b38ab --- /dev/null +++ b/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserCredentialAttributeEntity.java @@ -0,0 +1,113 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.keycloak.storage.jpa.entity; + +import org.keycloak.models.jpa.entities.CredentialEntity; + +import javax.persistence.Access; +import javax.persistence.AccessType; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.NamedQueries; +import javax.persistence.NamedQuery; +import javax.persistence.Table; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +@NamedQueries({ + @NamedQuery(name="deleteFederatedCredentialAttributeByCredential", query="delete from FederatedUserCredentialAttributeEntity attr where attr.credential = :credential"), + @NamedQuery(name="deleteFederatedCredentialAttributeByStorageProvider", query="delete from FederatedUserCredentialAttributeEntity attr where attr.credential IN (select cred from FederatedUserCredentialEntity cred where cred.storageProviderId=:storageProviderId)"), + @NamedQuery(name="deleteFederatedCredentialAttributeByRealm", query="delete from FederatedUserCredentialAttributeEntity attr where attr.credential IN (select cred from FederatedUserCredentialEntity cred where cred.realmId=:realmId)"), + @NamedQuery(name="deleteFederatedCredentialAttributeByRealmAndLink", query="delete from FederatedUserCredentialAttributeEntity attr where attr.credential IN (select cred from FederatedUserCredentialEntity cred where cred.userId IN (select u.id from UserEntity u where u.realmId=:realmId and u.federationLink=:link))"), + @NamedQuery(name="deleteFederatedCredentialAttributeByUser", query="delete from FederatedUserCredentialAttributeEntity attr where attr.credential IN (select cred from FederatedUserCredentialEntity cred where cred.userId = :userId and cred.realmId = :realmId)"), +}) +@Table(name="FED_CREDENTIAL_ATTRIBUTE") +@Entity +public class FederatedUserCredentialAttributeEntity { + + @Id + @Column(name="ID", length = 36) + @Access(AccessType.PROPERTY) // we do this because relationships often fetch id, but not entity. This avoids an extra SQL + protected String id; + + @ManyToOne(fetch= FetchType.LAZY) + @JoinColumn(name = "CREDENTIAL_ID") + protected FederatedUserCredentialEntity credential; + + @Column(name = "NAME") + protected String name; + @Column(name = "VALUE") + protected String value; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public FederatedUserCredentialEntity getCredential() { + return credential; + } + + public void setCredential(FederatedUserCredentialEntity credential) { + this.credential = credential; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null) return false; + if (!(o instanceof FederatedUserCredentialAttributeEntity)) return false; + + FederatedUserCredentialAttributeEntity that = (FederatedUserCredentialAttributeEntity) o; + + if (!id.equals(that.getId())) return false; + + return true; + } + + @Override + public int hashCode() { + return id.hashCode(); + } + +} diff --git a/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserCredentialEntity.java b/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserCredentialEntity.java index 996608b0b3..52a757cd8f 100755 --- a/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserCredentialEntity.java +++ b/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserCredentialEntity.java @@ -17,10 +17,12 @@ package org.keycloak.storage.jpa.entity; +import org.keycloak.models.jpa.entities.CredentialEntity; import org.keycloak.models.jpa.entities.UserEntity; import javax.persistence.Access; import javax.persistence.AccessType; +import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.FetchType; @@ -29,7 +31,10 @@ import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; import javax.persistence.NamedQueries; import javax.persistence.NamedQuery; +import javax.persistence.OneToMany; import javax.persistence.Table; +import java.util.ArrayList; +import java.util.Collection; /** * @author Bill Burke @@ -38,6 +43,7 @@ import javax.persistence.Table; @NamedQueries({ @NamedQuery(name="federatedUserCredentialByUser", query="select cred from FederatedUserCredentialEntity cred where cred.userId = :userId"), @NamedQuery(name="federatedUserCredentialByUserAndType", query="select cred from FederatedUserCredentialEntity cred where cred.userId = :userId and cred.type = :type"), + @NamedQuery(name="federatedUserCredentialByNameAndType", query="select cred from FederatedUserCredentialEntity cred where cred.userId = :userId and cred.type = :type and cred.device = :device"), @NamedQuery(name="deleteFederatedUserCredentialByUser", query="delete from FederatedUserCredentialEntity cred where cred.userId = :userId and cred.realmId = :realmId"), @NamedQuery(name="deleteFederatedUserCredentialByUserAndType", query="delete from FederatedUserCredentialEntity cred where cred.userId = :userId and cred.type = :type"), @NamedQuery(name="deleteFederatedUserCredentialByUserAndTypeAndDevice", query="delete from FederatedUserCredentialEntity cred where cred.userId = :userId and cred.type = :type and cred.device = :device"), @@ -87,6 +93,8 @@ public class FederatedUserCredentialEntity { protected int digits; @Column(name="PERIOD") protected int period; + @OneToMany(cascade = CascadeType.REMOVE, fetch = FetchType.EAGER, orphanRemoval = true, mappedBy="credential") + protected Collection credentialAttributes = new ArrayList<>(); public String getId() { @@ -201,6 +209,14 @@ public class FederatedUserCredentialEntity { this.period = period; } + public Collection getCredentialAttributes() { + return credentialAttributes; + } + + public void setCredentialAttributes(Collection credentialAttributes) { + this.credentialAttributes = credentialAttributes; + } + @Override public boolean equals(Object o) { if (this == o) return true; diff --git a/model/jpa/src/main/resources/META-INF/jpa-changelog-2.2.0.xml b/model/jpa/src/main/resources/META-INF/jpa-changelog-2.2.0.xml index 63afbb2dcf..adc49fbf3d 100755 --- a/model/jpa/src/main/resources/META-INF/jpa-changelog-2.2.0.xml +++ b/model/jpa/src/main/resources/META-INF/jpa-changelog-2.2.0.xml @@ -18,10 +18,44 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/model/jpa/src/main/resources/META-INF/persistence.xml b/model/jpa/src/main/resources/META-INF/persistence.xml index 0b2ff231ed..6288fe5e2a 100755 --- a/model/jpa/src/main/resources/META-INF/persistence.xml +++ b/model/jpa/src/main/resources/META-INF/persistence.xml @@ -22,6 +22,7 @@ org.keycloak.models.jpa.entities.ClientEntity org.keycloak.models.jpa.entities.CredentialEntity + org.keycloak.models.jpa.entities.CredentialAttributeEntity org.keycloak.models.jpa.entities.RealmEntity org.keycloak.models.jpa.entities.RealmAttributeEntity org.keycloak.models.jpa.entities.RequiredCredentialEntity @@ -74,6 +75,7 @@ org.keycloak.storage.jpa.entity.FederatedUserConsentRoleEntity org.keycloak.storage.jpa.entity.FederatedUserConsentProtocolMapperEntity org.keycloak.storage.jpa.entity.FederatedUserCredentialEntity + org.keycloak.storage.jpa.entity.FederatedUserCredentialAttributeEntity org.keycloak.storage.jpa.entity.FederatedUserGroupMembershipEntity org.keycloak.storage.jpa.entity.FederatedUserRequiredActionEntity org.keycloak.storage.jpa.entity.FederatedUserRoleMappingEntity diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProvider.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProvider.java index c0537ba082..9ad2c1ea36 100755 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProvider.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProvider.java @@ -24,6 +24,7 @@ import com.mongodb.QueryBuilder; import org.keycloak.component.ComponentModel; import org.keycloak.connections.mongo.api.MongoStore; import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext; +import org.keycloak.credential.CredentialInput; import org.keycloak.models.ClientModel; import org.keycloak.models.CredentialValidationOutput; import org.keycloak.models.FederatedIdentityModel; @@ -635,4 +636,5 @@ public class MongoUserProvider implements UserProvider { public void preRemove(RealmModel realm, ComponentModel component) { } + } diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java index e7dc5f60ce..86a90013cc 100755 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java @@ -1230,7 +1230,7 @@ public class RealmAdapter extends AbstractMongoAdapter impleme } updateRealm(); } - + @Override public boolean isAdminEventsEnabled() { return realm.isAdminEventsEnabled(); @@ -1240,7 +1240,7 @@ public class RealmAdapter extends AbstractMongoAdapter impleme public void setAdminEventsEnabled(boolean enabled) { realm.setAdminEventsEnabled(enabled); updateRealm(); - + } @Override @@ -1253,7 +1253,7 @@ public class RealmAdapter extends AbstractMongoAdapter impleme realm.setAdminEventsDetailsEnabled(enabled); updateRealm(); } - + @Override public ClientModel getMasterAdminClient() { MongoClientEntity appData = getMongoStore().loadEntity(MongoClientEntity.class, realm.getMasterAdminClient(), invocationContext); @@ -2181,4 +2181,62 @@ public class RealmAdapter extends AbstractMongoAdapter impleme } return null; } + + @Override + public void setAttribute(String name, String value) { + realm.getAttributes().put(name, value); + updateRealm(); + } + + @Override + public void setAttribute(String name, Boolean value) { + setAttribute(name, value.toString()); + } + + @Override + public void setAttribute(String name, Integer value) { + setAttribute(name, value.toString()); + } + + @Override + public void setAttribute(String name, Long value) { + setAttribute(name, value.toString()); + } + + @Override + public void removeAttribute(String name) { + realm.getAttributes().remove(name); + updateRealm(); + } + + @Override + public String getAttribute(String name) { + return realm.getAttributes().get(name); + } + + @Override + public Integer getAttribute(String name, Integer defaultValue) { + String v = getAttribute(name); + return v != null ? Integer.parseInt(v) : defaultValue; + } + + @Override + public Long getAttribute(String name, Long defaultValue) { + String v = getAttribute(name); + return v != null ? Long.parseLong(v) : defaultValue; + } + + @Override + public Boolean getAttribute(String name, Boolean defaultValue) { + String v = getAttribute(name); + return v != null ? Boolean.parseBoolean(v) : defaultValue; + } + + @Override + public Map getAttributes() { + Map attributes = new HashMap<>(); + attributes.putAll(realm.getAttributes()); + return attributes; + } + } diff --git a/pom.xml b/pom.xml index 7208837e46..da7a2ea4bd 100755 --- a/pom.xml +++ b/pom.xml @@ -13,6 +13,7 @@ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ~ See the License for the specific language governing permissions and ~ limitations under the License. + --> \u003Cdiv class="kc-logo-text"\u003E\u003Cspan\u003EKeycloak\u003C\u002Fspan\u003E\u003C\u002Fdiv\u003E ${project.version} ${timestamp} + community 7.0.0.Beta @@ -94,6 +96,7 @@ 1.3.7 1.0.2.Final 4.0.4 + 4.1.0 1.3.1b @@ -685,6 +688,17 @@ keycloak-kerberos-federation ${project.version} + + + org.keycloak + keycloak-sssd-federation + ${project.version} + + + net.java.dev.jna + jna + ${jna.version} + org.keycloak keycloak-ldap-federation @@ -1060,6 +1074,18 @@ ${project.version} zip + + org.keycloak + keycloak-fuse-adapter-dist + ${project.version} + zip + + + org.keycloak + keycloak-fuse-adapter-dist + ${project.version} + tar.gz + org.keycloak keycloak-wildfly-adapter-dist @@ -1101,12 +1127,6 @@ ${project.version} zip - - org.keycloak - keycloak-src-dist - ${project.version} - zip - org.keycloak keycloak-proxy-dist diff --git a/server-spi/src/main/java/org/keycloak/broker/provider/AbstractIdentityProviderFactory.java b/server-spi/src/main/java/org/keycloak/broker/provider/AbstractIdentityProviderFactory.java index e8be6d6192..953797b51c 100755 --- a/server-spi/src/main/java/org/keycloak/broker/provider/AbstractIdentityProviderFactory.java +++ b/server-spi/src/main/java/org/keycloak/broker/provider/AbstractIdentityProviderFactory.java @@ -50,7 +50,7 @@ public abstract class AbstractIdentityProviderFactory parseConfig(InputStream inputStream) { + public Map parseConfig(KeycloakSession session, InputStream inputStream) { return new HashMap(); } } diff --git a/server-spi/src/main/java/org/keycloak/broker/provider/IdentityProviderFactory.java b/server-spi/src/main/java/org/keycloak/broker/provider/IdentityProviderFactory.java index fba8e6431f..93ef720fad 100755 --- a/server-spi/src/main/java/org/keycloak/broker/provider/IdentityProviderFactory.java +++ b/server-spi/src/main/java/org/keycloak/broker/provider/IdentityProviderFactory.java @@ -17,6 +17,7 @@ package org.keycloak.broker.provider; import org.keycloak.models.IdentityProviderModel; +import org.keycloak.models.KeycloakSession; import org.keycloak.provider.ProviderFactory; import java.io.InputStream; @@ -47,8 +48,9 @@ public interface IdentityProviderFactory extends Pro *

Creates an {@link IdentityProvider} based on the configuration from * inputStream.

* + * @param session * @param inputStream The input stream from where configuration will be loaded from.. * @return */ - Map parseConfig(InputStream inputStream); + Map parseConfig(KeycloakSession session, InputStream inputStream); } \ No newline at end of file diff --git a/server-spi/src/main/java/org/keycloak/component/PrioritizedComponentModel.java b/server-spi/src/main/java/org/keycloak/component/PrioritizedComponentModel.java new file mode 100644 index 0000000000..7a0393a4bc --- /dev/null +++ b/server-spi/src/main/java/org/keycloak/component/PrioritizedComponentModel.java @@ -0,0 +1,58 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.keycloak.component; + +import org.keycloak.component.ComponentModel; +import org.keycloak.storage.UserStorageProviderModel; + +import java.util.Comparator; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class PrioritizedComponentModel extends ComponentModel { + public static Comparator comparator = new Comparator() { + @Override + public int compare(ComponentModel o1, ComponentModel o2) { + return parsePriority(o1) - parsePriority(o2); + } + }; + + public PrioritizedComponentModel(ComponentModel copy) { + super(copy); + } + + public PrioritizedComponentModel() { + } + + public static int parsePriority(ComponentModel component) { + String priority = component.getConfig().getFirst("priority"); + if (priority == null) return 0; + return Integer.valueOf(priority); + + } + + public int getPriority() { + return parsePriority(this); + + } + + public void setPriority(int priority) { + getConfig().putSingle("priority", Integer.toString(priority)); + } +} diff --git a/server-spi/src/main/java/org/keycloak/credential/CredentialInput.java b/server-spi/src/main/java/org/keycloak/credential/CredentialInput.java new file mode 100644 index 0000000000..805fb25d68 --- /dev/null +++ b/server-spi/src/main/java/org/keycloak/credential/CredentialInput.java @@ -0,0 +1,25 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.keycloak.credential; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public interface CredentialInput { + String getType(); +} diff --git a/server-spi/src/main/java/org/keycloak/credential/CredentialInputUpdater.java b/server-spi/src/main/java/org/keycloak/credential/CredentialInputUpdater.java new file mode 100644 index 0000000000..3456f8f5e1 --- /dev/null +++ b/server-spi/src/main/java/org/keycloak/credential/CredentialInputUpdater.java @@ -0,0 +1,32 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.keycloak.credential; + +import org.keycloak.models.RealmModel; +import org.keycloak.models.UserModel; + +import java.util.Set; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public interface CredentialInputUpdater { + boolean supportsCredentialType(String credentialType); + boolean updateCredential(RealmModel realm, UserModel user, CredentialInput input); + void disableCredentialType(RealmModel realm, UserModel user, String credentialType); +} diff --git a/server-spi/src/main/java/org/keycloak/credential/CredentialInputValidator.java b/server-spi/src/main/java/org/keycloak/credential/CredentialInputValidator.java new file mode 100644 index 0000000000..a7a4c6d58a --- /dev/null +++ b/server-spi/src/main/java/org/keycloak/credential/CredentialInputValidator.java @@ -0,0 +1,35 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.keycloak.credential; + +import org.keycloak.models.RealmModel; +import org.keycloak.models.UserModel; +import org.keycloak.provider.Provider; + +import java.util.List; +import java.util.Set; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public interface CredentialInputValidator { + boolean supportsCredentialType(String credentialType); + boolean isConfiguredFor(RealmModel realm, UserModel user, String credentialType); + boolean isValid(RealmModel realm, UserModel user, CredentialInput input); + +} diff --git a/server-spi/src/main/java/org/keycloak/credential/CredentialModel.java b/server-spi/src/main/java/org/keycloak/credential/CredentialModel.java new file mode 100755 index 0000000000..24b7772398 --- /dev/null +++ b/server-spi/src/main/java/org/keycloak/credential/CredentialModel.java @@ -0,0 +1,158 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.keycloak.credential; + +import org.keycloak.common.util.MultivaluedHashMap; + +import java.io.Serializable; +import java.util.List; +import java.util.Map; + +/** + * Used just in cases when we want to "directly" update or retrieve the hash or salt of user credential (For example during export/import) + * + * @author Marek Posolda + */ +public class CredentialModel implements Serializable { + public static final String PASSWORD = "password"; + public static final String PASSWORD_HISTORY = "password-history"; + public static final String PASSWORD_TOKEN = "password-token"; + + // Secret is same as password but it is not hashed + public static final String SECRET = "secret"; + public static final String TOTP = "totp"; + public static final String HOTP = "hotp"; + public static final String CLIENT_CERT = "cert"; + public static final String KERBEROS = "kerberos"; + public static final String OTP = "otp"; + + + + private String id; + private String type; + private String value; + private String device; + private byte[] salt; + private int hashIterations; + private Long createdDate; + + // otp stuff + private int counter; + private String algorithm; + private int digits; + private int period; + private MultivaluedHashMap config; + + + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public String getDevice() { + return device; + } + + public void setDevice(String device) { + this.device = device; + } + + public byte[] getSalt() { + return salt; + } + + public void setSalt(byte[] salt) { + this.salt = salt; + } + + public int getHashIterations() { + return hashIterations; + } + + public void setHashIterations(int iterations) { + this.hashIterations = iterations; + } + + public Long getCreatedDate() { + return createdDate; + } + + public void setCreatedDate(Long createdDate) { + this.createdDate = createdDate; + } + + public int getCounter() { + return counter; + } + + public void setCounter(int counter) { + this.counter = counter; + } + + public String getAlgorithm() { + return algorithm; + } + + public void setAlgorithm(String algorithm) { + this.algorithm = algorithm; + } + + public int getDigits() { + return digits; + } + + public void setDigits(int digits) { + this.digits = digits; + } + + public int getPeriod() { + return period; + } + + public void setPeriod(int period) { + this.period = period; + } + + public MultivaluedHashMap getConfig() { + return config; + } + + public void setConfig(MultivaluedHashMap config) { + this.config = config; + } +} diff --git a/server-spi/src/main/java/org/keycloak/credential/CredentialProvider.java b/server-spi/src/main/java/org/keycloak/credential/CredentialProvider.java new file mode 100644 index 0000000000..a8304339c5 --- /dev/null +++ b/server-spi/src/main/java/org/keycloak/credential/CredentialProvider.java @@ -0,0 +1,31 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.keycloak.credential; + +import org.keycloak.provider.Provider; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public interface CredentialProvider extends Provider { + @Override + default + void close() { + + } +} diff --git a/server-spi/src/main/java/org/keycloak/credential/CredentialProviderFactory.java b/server-spi/src/main/java/org/keycloak/credential/CredentialProviderFactory.java new file mode 100755 index 0000000000..480dd1c5c2 --- /dev/null +++ b/server-spi/src/main/java/org/keycloak/credential/CredentialProviderFactory.java @@ -0,0 +1,85 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.keycloak.credential; + +import org.keycloak.Config; +import org.keycloak.component.ComponentFactory; +import org.keycloak.component.ComponentModel; +import org.keycloak.component.ComponentValidationException; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.KeycloakSessionFactory; +import org.keycloak.provider.ProviderConfigProperty; +import org.keycloak.storage.UserStorageProvider; + +import java.util.Collections; +import java.util.List; +import java.util.Set; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public interface CredentialProviderFactory extends ComponentFactory { + /** + * called per Keycloak transaction. + * + * @param session + * @param model + * @return + */ + T create(KeycloakSession session, ComponentModel model); + + /** + * This is the name of the provider and will be showed in the admin console as an option. + * + * @return + */ + @Override + String getId(); + + @Override + default void init(Config.Scope config) { + + } + + @Override + default void postInit(KeycloakSessionFactory factory) { + + } + + @Override + default void close() { + + } + + @Override + default String getHelpText() { + return ""; + } + + @Override + default List getConfigProperties() { + return Collections.EMPTY_LIST; + } + + @Override + default void validateConfiguration(KeycloakSession session, ComponentModel config) throws ComponentValidationException { + + } + +} diff --git a/server-spi/src/main/java/org/keycloak/credential/UserCredentialStore.java b/server-spi/src/main/java/org/keycloak/credential/UserCredentialStore.java new file mode 100644 index 0000000000..c99873911c --- /dev/null +++ b/server-spi/src/main/java/org/keycloak/credential/UserCredentialStore.java @@ -0,0 +1,37 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.keycloak.credential; + +import org.keycloak.models.RealmModel; +import org.keycloak.models.UserModel; +import org.keycloak.provider.Provider; + +import java.util.List; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public interface UserCredentialStore extends Provider { + void updateCredential(RealmModel realm, UserModel user, CredentialModel cred); + CredentialModel createCredential(RealmModel realm, UserModel user, CredentialModel cred); + boolean removeStoredCredential(RealmModel realm, UserModel user, String id); + CredentialModel getStoredCredentialById(RealmModel realm, UserModel user, String id); + List getStoredCredentials(RealmModel realm, UserModel user); + List getStoredCredentialsByType(RealmModel realm, UserModel user, String type); + CredentialModel getStoredCredentialByNameAndType(RealmModel realm, UserModel user, String name, String type); +} diff --git a/server-spi/src/main/java/org/keycloak/migration/MigrationModel.java b/server-spi/src/main/java/org/keycloak/migration/MigrationModel.java index c2e6dab0ec..a59508b4f4 100755 --- a/server-spi/src/main/java/org/keycloak/migration/MigrationModel.java +++ b/server-spi/src/main/java/org/keycloak/migration/MigrationModel.java @@ -23,11 +23,6 @@ package org.keycloak.migration; * @version $Revision: 1 $ */ public interface MigrationModel { - /** - * Must have the form of major.minor.micro as the version is parsed and numbers are compared - */ - String LATEST_VERSION = "2.1.0"; - String getStoredVersion(); void setStoredVersion(String version); } diff --git a/server-spi/src/main/java/org/keycloak/migration/MigrationModelManager.java b/server-spi/src/main/java/org/keycloak/migration/MigrationModelManager.java index 6a2f448b58..e2b55e139e 100755 --- a/server-spi/src/main/java/org/keycloak/migration/MigrationModelManager.java +++ b/server-spi/src/main/java/org/keycloak/migration/MigrationModelManager.java @@ -18,6 +18,7 @@ package org.keycloak.migration; import org.jboss.logging.Logger; +import org.keycloak.migration.migrators.MigrateTo1_2_0; import org.keycloak.migration.migrators.MigrateTo1_3_0; import org.keycloak.migration.migrators.MigrateTo1_4_0; import org.keycloak.migration.migrators.MigrateTo1_5_0; @@ -28,7 +29,8 @@ import org.keycloak.migration.migrators.MigrateTo1_9_0; import org.keycloak.migration.migrators.MigrateTo1_9_2; import org.keycloak.migration.migrators.MigrateTo2_0_0; import org.keycloak.migration.migrators.MigrateTo2_1_0; -import org.keycloak.migration.migrators.MigrationTo1_2_0_CR1; +import org.keycloak.migration.migrators.MigrateTo2_2_0; +import org.keycloak.migration.migrators.Migration; import org.keycloak.models.KeycloakSession; /** @@ -38,82 +40,41 @@ import org.keycloak.models.KeycloakSession; public class MigrationModelManager { private static Logger logger = Logger.getLogger(MigrationModelManager.class); + private static final Migration[] migrations = { + new MigrateTo1_2_0(), + new MigrateTo1_3_0(), + new MigrateTo1_4_0(), + new MigrateTo1_5_0(), + new MigrateTo1_6_0(), + new MigrateTo1_7_0(), + new MigrateTo1_8_0(), + new MigrateTo1_9_0(), + new MigrateTo1_9_2(), + new MigrateTo2_0_0(), + new MigrateTo2_1_0(), + new MigrateTo2_2_0(), + }; + public static void migrate(KeycloakSession session) { + ModelVersion latest = migrations[migrations.length-1].getVersion(); MigrationModel model = session.realms().getMigrationModel(); - String storedVersion = model.getStoredVersion(); - if (MigrationModel.LATEST_VERSION.equals(storedVersion)) return; ModelVersion stored = null; - if (storedVersion != null) { - stored = new ModelVersion(storedVersion); + if (model.getStoredVersion() != null) { + stored = new ModelVersion(model.getStoredVersion()); + if (latest.equals(stored)) { + return; + } } - if (stored == null || stored.lessThan(MigrationTo1_2_0_CR1.VERSION)) { - if (stored != null) { - logger.debug("Migrating older model to 1.2.0.CR1 updates"); + for (Migration m : migrations) { + if (stored == null || stored.lessThan(m.getVersion())) { + if (stored != null) { + logger.debugf("Migrating older model to %s", m.getVersion()); + } + m.migrate(session); } - new MigrationTo1_2_0_CR1().migrate(session); - } - if (stored == null || stored.lessThan(MigrateTo1_3_0.VERSION)) { - if (stored != null) { - logger.debug("Migrating older model to 1.3.0 updates"); - } - new MigrateTo1_3_0().migrate(session); - } - if (stored == null || stored.lessThan(MigrateTo1_4_0.VERSION)) { - if (stored != null) { - logger.debug("Migrating older model to 1.4.0 updates"); - } - new MigrateTo1_4_0().migrate(session); - } - if (stored == null || stored.lessThan(MigrateTo1_5_0.VERSION)) { - if (stored != null) { - logger.debug("Migrating older model to 1.5.0 updates"); - } - new MigrateTo1_5_0().migrate(session); - } - if (stored == null || stored.lessThan(MigrateTo1_6_0.VERSION)) { - if (stored != null) { - logger.debug("Migrating older model to 1.6.0 updates"); - } - new MigrateTo1_6_0().migrate(session); - } - if (stored == null || stored.lessThan(MigrateTo1_7_0.VERSION)) { - if (stored != null) { - logger.debug("Migrating older model to 1.7.0 updates"); - } - new MigrateTo1_7_0().migrate(session); - } - if (stored == null || stored.lessThan(MigrateTo1_8_0.VERSION)) { - if (stored != null) { - logger.debug("Migrating older model to 1.8.0 updates"); - } - new MigrateTo1_8_0().migrate(session); - } - if (stored == null || stored.lessThan(MigrateTo1_9_0.VERSION)) { - if (stored != null) { - logger.debug("Migrating older model to 1.9.0 updates"); - } - new MigrateTo1_9_0().migrate(session); - } - if (stored == null || stored.lessThan(MigrateTo1_9_2.VERSION)) { - if (stored != null) { - logger.debug("Migrating older model to 1.9.2 updates"); - } - new MigrateTo1_9_2().migrate(session); - } - if (stored == null || stored.lessThan(MigrateTo2_0_0.VERSION)) { - if (stored != null) { - logger.debug("Migrating older model to 2.0.0 updates"); - } - new MigrateTo2_0_0().migrate(session); - } - if (stored == null || stored.lessThan(MigrateTo2_1_0.VERSION)) { - if (stored != null) { - logger.debug("Migrating older model to 2.1.0 updates"); - } - new MigrateTo2_1_0().migrate(session); } - model.setStoredVersion(MigrationModel.LATEST_VERSION); + model.setStoredVersion(latest.toString()); } } diff --git a/server-spi/src/main/java/org/keycloak/migration/ModelVersion.java b/server-spi/src/main/java/org/keycloak/migration/ModelVersion.java index 383edd5e80..884587917e 100755 --- a/server-spi/src/main/java/org/keycloak/migration/ModelVersion.java +++ b/server-spi/src/main/java/org/keycloak/migration/ModelVersion.java @@ -98,4 +98,19 @@ public class ModelVersion { if (comp < 0) return true; return false; } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof ModelVersion)) { + return false; + } + + ModelVersion v = (ModelVersion) obj; + return v.getMajor() == major && v.getMinor() == minor && v.getMicro() != micro; + } + + @Override + public String toString() { + return major + "." + minor + "." + micro; + } } diff --git a/server-spi/src/main/java/org/keycloak/migration/migrators/MigrationTo1_2_0_CR1.java b/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo1_2_0.java similarity index 94% rename from server-spi/src/main/java/org/keycloak/migration/migrators/MigrationTo1_2_0_CR1.java rename to server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo1_2_0.java index f2a86ce466..d363a10e53 100755 --- a/server-spi/src/main/java/org/keycloak/migration/migrators/MigrationTo1_2_0_CR1.java +++ b/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo1_2_0.java @@ -32,8 +32,13 @@ import java.util.Map; * @author Bill Burke * @version $Revision: 1 $ */ -public class MigrationTo1_2_0_CR1 { - public static final ModelVersion VERSION = new ModelVersion("1.2.0.CR1"); +public class MigrateTo1_2_0 implements Migration { + public static final ModelVersion VERSION = new ModelVersion("1.2.0"); + + @Override + public ModelVersion getVersion() { + return VERSION; + } public void setupBrokerService(RealmModel realm) { ClientModel client = realm.getClientByClientId(Constants.BROKER_SERVICE_CLIENT_ID); diff --git a/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo1_3_0.java b/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo1_3_0.java index 8d573c7afa..ee337147aa 100755 --- a/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo1_3_0.java +++ b/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo1_3_0.java @@ -37,9 +37,13 @@ import javax.naming.directory.SearchControls; * @author Bill Burke * @version $Revision: 1 $ */ -public class MigrateTo1_3_0 { +public class MigrateTo1_3_0 implements Migration { + public static final ModelVersion VERSION = new ModelVersion("1.3.0"); + public ModelVersion getVersion() { + return VERSION; + } public void migrate(KeycloakSession session) { List realms = session.realms().getRealms(); diff --git a/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo1_4_0.java b/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo1_4_0.java index 5fbab26194..9f28f91918 100755 --- a/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo1_4_0.java +++ b/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo1_4_0.java @@ -34,9 +34,13 @@ import java.util.List; * @author Bill Burke * @version $Revision: 1 $ */ -public class MigrateTo1_4_0 { +public class MigrateTo1_4_0 implements Migration { public static final ModelVersion VERSION = new ModelVersion("1.4.0"); + public ModelVersion getVersion() { + return VERSION; + } + public void migrate(KeycloakSession session) { List realms = session.realms().getRealms(); for (RealmModel realm : realms) { diff --git a/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo1_5_0.java b/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo1_5_0.java index 1a7f83e670..6969d5c30c 100755 --- a/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo1_5_0.java +++ b/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo1_5_0.java @@ -32,9 +32,13 @@ import java.util.List; * @author Bill Burke * @version $Revision: 1 $ */ -public class MigrateTo1_5_0 { +public class MigrateTo1_5_0 implements Migration { public static final ModelVersion VERSION = new ModelVersion("1.5.0"); + public ModelVersion getVersion() { + return VERSION; + } + public void migrate(KeycloakSession session) { List realms = session.realms().getRealms(); for (RealmModel realm : realms) { diff --git a/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo1_6_0.java b/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo1_6_0.java index 17ddf1b37f..c3d922258d 100644 --- a/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo1_6_0.java +++ b/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo1_6_0.java @@ -27,10 +27,14 @@ import org.keycloak.models.utils.KeycloakModelUtils; /** * @author Marek Posolda */ -public class MigrateTo1_6_0 { +public class MigrateTo1_6_0 implements Migration { public static final ModelVersion VERSION = new ModelVersion("1.6.0"); + public ModelVersion getVersion() { + return VERSION; + } + public void migrate(KeycloakSession session) { MigrationProvider provider = session.getProvider(MigrationProvider.class); diff --git a/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo1_7_0.java b/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo1_7_0.java index 47abe62a37..3d4a5d5c9f 100644 --- a/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo1_7_0.java +++ b/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo1_7_0.java @@ -31,10 +31,14 @@ import org.keycloak.models.utils.DefaultAuthenticationFlows; /** * @author Marek Posolda */ -public class MigrateTo1_7_0 { +public class MigrateTo1_7_0 implements Migration { public static final ModelVersion VERSION = new ModelVersion("1.7.0"); + public ModelVersion getVersion() { + return VERSION; + } + public void migrate(KeycloakSession session) { List realms = session.realms().getRealms(); for (RealmModel realm : realms) { diff --git a/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo1_8_0.java b/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo1_8_0.java index 94e0243c12..549cde9a7c 100644 --- a/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo1_8_0.java +++ b/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo1_8_0.java @@ -30,10 +30,14 @@ import org.keycloak.models.utils.KeycloakModelUtils; /** * @author Marek Posolda */ -public class MigrateTo1_8_0 { +public class MigrateTo1_8_0 implements Migration { public static final ModelVersion VERSION = new ModelVersion("1.8.0"); + public ModelVersion getVersion() { + return VERSION; + } + public void migrate(KeycloakSession session) { List realms = session.realms().getRealms(); for (RealmModel realm : realms) { diff --git a/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo1_9_0.java b/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo1_9_0.java index fb24598afa..e91f1265b1 100644 --- a/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo1_9_0.java +++ b/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo1_9_0.java @@ -33,10 +33,14 @@ import java.util.Map; /** * @author Marek Posolda */ -public class MigrateTo1_9_0 { +public class MigrateTo1_9_0 implements Migration { public static final ModelVersion VERSION = new ModelVersion("1.9.0"); + public ModelVersion getVersion() { + return VERSION; + } + public void migrate(KeycloakSession session) { RealmModel realm = session.realms().getRealm(Config.getAdminRealm()); if (realm != null && realm.getDisplayNameHtml() != null && realm.getDisplayNameHtml().equals("Keycloak")) { diff --git a/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo1_9_2.java b/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo1_9_2.java index 2473937382..3a41058c81 100644 --- a/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo1_9_2.java +++ b/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo1_9_2.java @@ -25,10 +25,14 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; -public class MigrateTo1_9_2 { +public class MigrateTo1_9_2 implements Migration { public static final ModelVersion VERSION = new ModelVersion("1.9.2"); + public ModelVersion getVersion() { + return VERSION; + } + public void migrate(KeycloakSession session) { for (RealmModel realm : session.realms().getRealms()) { if (realm.getBrowserSecurityHeaders() != null) { diff --git a/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo2_0_0.java b/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo2_0_0.java index 23368f3130..d36a8583c7 100644 --- a/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo2_0_0.java +++ b/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo2_0_0.java @@ -23,10 +23,14 @@ import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; import org.keycloak.models.utils.KeycloakModelUtils; -public class MigrateTo2_0_0 { +public class MigrateTo2_0_0 implements Migration { public static final ModelVersion VERSION = new ModelVersion("2.0.0"); + public ModelVersion getVersion() { + return VERSION; + } + public void migrate(KeycloakSession session) { for (RealmModel realm : session.realms().getRealms()) { migrateAuthorizationServices(realm); diff --git a/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo2_1_0.java b/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo2_1_0.java index 9e7b93130d..995dafb3fb 100644 --- a/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo2_1_0.java +++ b/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo2_1_0.java @@ -38,9 +38,13 @@ import java.util.stream.Collectors; * * @author Stan Silvert ssilvert@redhat.com (C) 2016 Red Hat Inc. */ -public class MigrateTo2_1_0 { +public class MigrateTo2_1_0 implements Migration { public static final ModelVersion VERSION = new ModelVersion("2.1.0"); + public ModelVersion getVersion() { + return VERSION; + } + public void migrate(KeycloakSession session) { for (RealmModel realm : session.realms().getRealms()) { migrateDefaultRequiredAction(realm); diff --git a/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo2_2_0.java b/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo2_2_0.java new file mode 100644 index 0000000000..1dbcba4200 --- /dev/null +++ b/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo2_2_0.java @@ -0,0 +1,59 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.keycloak.migration.migrators; + +import org.jboss.logging.Logger; +import org.keycloak.migration.ModelVersion; +import org.keycloak.models.AuthenticationExecutionModel; +import org.keycloak.models.AuthenticatorConfigModel; +import org.keycloak.models.IdentityProviderModel; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.RealmModel; +import org.keycloak.models.utils.DefaultAuthenticationFlows; + +import java.util.HashMap; +import java.util.Map; + +public class MigrateTo2_2_0 implements Migration { + public static final ModelVersion VERSION = new ModelVersion("2.2.0"); + + private static final Logger LOG = Logger.getLogger(MigrateTo2_2_0.class); + + public ModelVersion getVersion() { + return VERSION; + } + + public void migrate(KeycloakSession session) { + for (RealmModel realm : session.realms().getRealms()) { + addIdentityProviderAuthenticator(realm); + } + } + + private void addIdentityProviderAuthenticator(RealmModel realm) { + String defaultProvider = null; + for (IdentityProviderModel provider : realm.getIdentityProviders()) { + if (provider.isEnabled() && provider.isAuthenticateByDefault()) { + defaultProvider = provider.getAlias(); + break; + } + } + + DefaultAuthenticationFlows.addIdentityProviderAuthenticator(realm, defaultProvider); + } + +} diff --git a/server-spi/src/main/java/org/keycloak/migration/migrators/Migration.java b/server-spi/src/main/java/org/keycloak/migration/migrators/Migration.java new file mode 100644 index 0000000000..e37dad29f9 --- /dev/null +++ b/server-spi/src/main/java/org/keycloak/migration/migrators/Migration.java @@ -0,0 +1,32 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.keycloak.migration.migrators; + +import org.keycloak.migration.ModelVersion; +import org.keycloak.models.KeycloakSession; + +/** + * @author Stian Thorgersen + */ +public interface Migration { + + void migrate(KeycloakSession session); + + ModelVersion getVersion(); + +} diff --git a/server-spi/src/main/java/org/keycloak/models/Constants.java b/server-spi/src/main/java/org/keycloak/models/Constants.java index 916565aaa4..edc9567e37 100755 --- a/server-spi/src/main/java/org/keycloak/models/Constants.java +++ b/server-spi/src/main/java/org/keycloak/models/Constants.java @@ -50,5 +50,8 @@ public interface Constants { String KEY = "key"; // Prefix for user attributes used in various "context"data maps - public static final String USER_ATTRIBUTES_PREFIX = "user.attributes."; + String USER_ATTRIBUTES_PREFIX = "user.attributes."; + + // Indication to admin-rest-endpoint that realm keys should be re-generated + String GENERATE = "GENERATE"; } diff --git a/server-spi/src/main/java/org/keycloak/models/IdentityProviderModel.java b/server-spi/src/main/java/org/keycloak/models/IdentityProviderModel.java index 82e373f77f..2425c7d877 100755 --- a/server-spi/src/main/java/org/keycloak/models/IdentityProviderModel.java +++ b/server-spi/src/main/java/org/keycloak/models/IdentityProviderModel.java @@ -120,10 +120,12 @@ public class IdentityProviderModel implements Serializable { this.storeToken = storeToken; } + @Deprecated public boolean isAuthenticateByDefault() { return authenticateByDefault; } + @Deprecated public void setAuthenticateByDefault(boolean authenticateByDefault) { this.authenticateByDefault = authenticateByDefault; } diff --git a/server-spi/src/main/java/org/keycloak/models/KeycloakSession.java b/server-spi/src/main/java/org/keycloak/models/KeycloakSession.java index 8b6dcd31d0..2a1ad0ff4c 100755 --- a/server-spi/src/main/java/org/keycloak/models/KeycloakSession.java +++ b/server-spi/src/main/java/org/keycloak/models/KeycloakSession.java @@ -17,6 +17,7 @@ package org.keycloak.models; +import org.keycloak.models.cache.UserCache; import org.keycloak.provider.Provider; import org.keycloak.scripting.ScriptingProvider; import org.keycloak.storage.federated.UserFederatedStorageProvider; @@ -74,6 +75,7 @@ public interface KeycloakSession { Object removeAttribute(String attribute); void setAttribute(String name, Object value); + void enlistForClose(Provider provider); KeycloakSessionFactory getKeycloakSessionFactory(); @@ -101,7 +103,14 @@ public interface KeycloakSession { void close(); /** - * A cached view of all users in system. + * The user cache + * + * @return may be null if cache is disabled + */ + UserCache getUserCache(); + + /** + * A possibly cached view of all users in system. * * @return */ @@ -115,8 +124,10 @@ public interface KeycloakSession { */ UserProvider userStorageManager(); + UserCredentialManager userCredentialManager(); + /** - * A cached view of all users in system that does NOT include users available from the deprecated UserFederationProvider SPI. + * A possibly cached view of all users in system that does NOT include users available from the deprecated UserFederationProvider SPI. */ UserProvider userStorage(); @@ -129,6 +140,7 @@ public interface KeycloakSession { /** * Hybrid storage for UserStorageProviders that can't store a specific piece of keycloak data in their external storage. + * No cache in front. * * @return */ diff --git a/server-spi/src/main/java/org/keycloak/models/KeycloakTransactionManager.java b/server-spi/src/main/java/org/keycloak/models/KeycloakTransactionManager.java index 0e2dcbea97..a18d8cfd96 100755 --- a/server-spi/src/main/java/org/keycloak/models/KeycloakTransactionManager.java +++ b/server-spi/src/main/java/org/keycloak/models/KeycloakTransactionManager.java @@ -23,6 +23,21 @@ package org.keycloak.models; */ public interface KeycloakTransactionManager extends KeycloakTransaction { + enum JTAPolicy { + /** + * Do not interact with JTA at all + * + */ + NOT_SUPPORTED, + /** + * A new JTA Transaction will be created when Keycloak TM begin() is called. If an existing JTA transaction + * exists, it is suspended and resumed after the Keycloak transaction finishes. + */ + REQUIRES_NEW, + } + + JTAPolicy getJTAPolicy(); + void setJTAPolicy(JTAPolicy policy); void enlist(KeycloakTransaction transaction); void enlistAfterCompletion(KeycloakTransaction transaction); diff --git a/server-spi/src/main/java/org/keycloak/models/RealmModel.java b/server-spi/src/main/java/org/keycloak/models/RealmModel.java index 61dc9c10f8..7ff7813a8c 100755 --- a/server-spi/src/main/java/org/keycloak/models/RealmModel.java +++ b/server-spi/src/main/java/org/keycloak/models/RealmModel.java @@ -110,6 +110,17 @@ public interface RealmModel extends RoleContainerModel { void setEditUsernameAllowed(boolean editUsernameAllowed); + void setAttribute(String name, String value); + void setAttribute(String name, Boolean value); + void setAttribute(String name, Integer value); + void setAttribute(String name, Long value); + void removeAttribute(String name); + String getAttribute(String name); + Integer getAttribute(String name, Integer defaultValue); + Long getAttribute(String name, Long defaultValue); + Boolean getAttribute(String name, Boolean defaultValue); + Map getAttributes(); + //--- brute force settings boolean isBruteForceProtected(); void setBruteForceProtected(boolean value); diff --git a/server-spi/src/main/java/org/keycloak/models/UserCredentialManager.java b/server-spi/src/main/java/org/keycloak/models/UserCredentialManager.java new file mode 100644 index 0000000000..2da5716f92 --- /dev/null +++ b/server-spi/src/main/java/org/keycloak/models/UserCredentialManager.java @@ -0,0 +1,37 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.keycloak.models; + +import org.keycloak.credential.CredentialInput; +import org.keycloak.credential.UserCredentialStore; + +import java.util.List; +import java.util.Set; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public interface UserCredentialManager extends UserCredentialStore { + boolean isValid(RealmModel realm, UserModel user, List inputs); + + void updateCredential(RealmModel realm, UserModel user, CredentialInput input); + void disableCredential(RealmModel realm, UserModel user, String credentialType); + + boolean isConfiguredFor(RealmModel realm, UserModel user, String type); + +} diff --git a/server-spi/src/main/java/org/keycloak/models/UserCredentialModel.java b/server-spi/src/main/java/org/keycloak/models/UserCredentialModel.java index 3e03508bda..4be355d19a 100755 --- a/server-spi/src/main/java/org/keycloak/models/UserCredentialModel.java +++ b/server-spi/src/main/java/org/keycloak/models/UserCredentialModel.java @@ -17,23 +17,26 @@ package org.keycloak.models; +import org.keycloak.credential.CredentialInput; +import org.keycloak.credential.CredentialModel; + import java.util.UUID; /** * @author Bill Burke * @version $Revision: 1 $ */ -public class UserCredentialModel { - public static final String PASSWORD = "password"; - public static final String PASSWORD_HISTORY = "password-history"; - public static final String PASSWORD_TOKEN = "password-token"; +public class UserCredentialModel implements CredentialInput { + public static final String PASSWORD = CredentialModel.PASSWORD; + public static final String PASSWORD_HISTORY = CredentialModel.PASSWORD_HISTORY; + public static final String PASSWORD_TOKEN = CredentialModel.PASSWORD_TOKEN; // Secret is same as password but it is not hashed - public static final String SECRET = "secret"; - public static final String TOTP = "totp"; - public static final String HOTP = "hotp"; - public static final String CLIENT_CERT = "cert"; - public static final String KERBEROS = "kerberos"; + public static final String SECRET = CredentialModel.SECRET; + public static final String TOTP = CredentialModel.TOTP; + public static final String HOTP = CredentialModel.HOTP; + public static final String CLIENT_CERT = CredentialModel.CLIENT_CERT; + public static final String KERBEROS = CredentialModel.KERBEROS; protected String type; protected String value; diff --git a/server-spi/src/main/java/org/keycloak/models/UserFederationManager.java b/server-spi/src/main/java/org/keycloak/models/UserFederationManager.java index cb6867ac57..e1037053fc 100755 --- a/server-spi/src/main/java/org/keycloak/models/UserFederationManager.java +++ b/server-spi/src/main/java/org/keycloak/models/UserFederationManager.java @@ -19,6 +19,7 @@ package org.keycloak.models; import org.jboss.logging.Logger; import org.keycloak.component.ComponentModel; +import org.keycloak.credential.CredentialInput; import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.policy.PasswordPolicyManagerProvider; import org.keycloak.policy.PolicyError; diff --git a/server-spi/src/main/java/org/keycloak/models/UserProvider.java b/server-spi/src/main/java/org/keycloak/models/UserProvider.java index d6ef2cd360..642131b518 100755 --- a/server-spi/src/main/java/org/keycloak/models/UserProvider.java +++ b/server-spi/src/main/java/org/keycloak/models/UserProvider.java @@ -18,6 +18,7 @@ package org.keycloak.models; import org.keycloak.component.ComponentModel; +import org.keycloak.credential.CredentialInput; import org.keycloak.provider.Provider; import org.keycloak.storage.user.UserCredentialValidatorProvider; import org.keycloak.storage.user.UserLookupProvider; @@ -85,4 +86,5 @@ public interface UserProvider extends Provider, void close(); void preRemove(RealmModel realm, ComponentModel component); + } diff --git a/server-spi/src/main/java/org/keycloak/models/cache/CacheUserProviderSpi.java b/server-spi/src/main/java/org/keycloak/models/cache/CacheUserProviderSpi.java index c6723fba97..a0efa39e91 100755 --- a/server-spi/src/main/java/org/keycloak/models/cache/CacheUserProviderSpi.java +++ b/server-spi/src/main/java/org/keycloak/models/cache/CacheUserProviderSpi.java @@ -39,11 +39,11 @@ public class CacheUserProviderSpi implements Spi { @Override public Class getProviderClass() { - return CacheUserProvider.class; + return UserCache.class; } @Override public Class getProviderFactoryClass() { - return CacheUserProviderFactory.class; + return UserCacheProviderFactory.class; } } diff --git a/server-spi/src/main/java/org/keycloak/models/cache/CachedUserModel.java b/server-spi/src/main/java/org/keycloak/models/cache/CachedUserModel.java new file mode 100644 index 0000000000..5d9c7cd278 --- /dev/null +++ b/server-spi/src/main/java/org/keycloak/models/cache/CachedUserModel.java @@ -0,0 +1,43 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.keycloak.models.cache; + +import org.keycloak.models.UserModel; + +import java.util.concurrent.ConcurrentHashMap; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public interface CachedUserModel extends UserModel { + void invalidate(); + + /** + * When was the user loaded from database. + * + * @return + */ + long getCacheTimestamp(); + + /** + * Returns a map that contains custom things that are cached along with the user. You can write to this map. + * + * @return + */ + ConcurrentHashMap getCachedWith(); +} diff --git a/server-spi/src/main/java/org/keycloak/models/cache/CacheUserProvider.java b/server-spi/src/main/java/org/keycloak/models/cache/OnUserCache.java old mode 100755 new mode 100644 similarity index 85% rename from server-spi/src/main/java/org/keycloak/models/cache/CacheUserProvider.java rename to server-spi/src/main/java/org/keycloak/models/cache/OnUserCache.java index 63c97061d6..e676ce14e6 --- a/server-spi/src/main/java/org/keycloak/models/cache/CacheUserProvider.java +++ b/server-spi/src/main/java/org/keycloak/models/cache/OnUserCache.java @@ -14,17 +14,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package org.keycloak.models.cache; import org.keycloak.models.RealmModel; -import org.keycloak.models.UserProvider; /** * @author Bill Burke * @version $Revision: 1 $ */ -public interface CacheUserProvider extends UserProvider { - void clear(); - UserProvider getDelegate(); +public interface OnUserCache { + void onCache(RealmModel realm, CachedUserModel user); } diff --git a/server-spi/src/main/java/org/keycloak/models/cache/UserCache.java b/server-spi/src/main/java/org/keycloak/models/cache/UserCache.java new file mode 100755 index 0000000000..f309079d94 --- /dev/null +++ b/server-spi/src/main/java/org/keycloak/models/cache/UserCache.java @@ -0,0 +1,36 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.keycloak.models.cache; + +import org.keycloak.models.RealmModel; +import org.keycloak.models.UserModel; +import org.keycloak.models.UserProvider; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public interface UserCache extends UserProvider { + /** + * Evict user from cache. + * + * @param user + */ + void evict(RealmModel realm, UserModel user); + void clear(); +} diff --git a/server-spi/src/main/java/org/keycloak/models/cache/CacheUserProviderFactory.java b/server-spi/src/main/java/org/keycloak/models/cache/UserCacheProviderFactory.java similarity index 90% rename from server-spi/src/main/java/org/keycloak/models/cache/CacheUserProviderFactory.java rename to server-spi/src/main/java/org/keycloak/models/cache/UserCacheProviderFactory.java index 37208e4711..fdf4fd881d 100755 --- a/server-spi/src/main/java/org/keycloak/models/cache/CacheUserProviderFactory.java +++ b/server-spi/src/main/java/org/keycloak/models/cache/UserCacheProviderFactory.java @@ -23,6 +23,6 @@ import org.keycloak.provider.ProviderFactory; * @author Bill Burke * @version $Revision: 1 $ */ -public interface CacheUserProviderFactory extends ProviderFactory { +public interface UserCacheProviderFactory extends ProviderFactory { } diff --git a/server-spi/src/main/java/org/keycloak/models/entities/RealmEntity.java b/server-spi/src/main/java/org/keycloak/models/entities/RealmEntity.java index c5a6ecf8fe..cccdacdaf3 100755 --- a/server-spi/src/main/java/org/keycloak/models/entities/RealmEntity.java +++ b/server-spi/src/main/java/org/keycloak/models/entities/RealmEntity.java @@ -94,6 +94,8 @@ public class RealmEntity extends AbstractIdentifiableEntity { private Map smtpConfig = new HashMap(); private Map socialConfig = new HashMap(); + private Map attributes = new HashMap<>(); + private boolean eventsEnabled; private long eventsExpiration; private List eventsListeners = new ArrayList(); @@ -692,6 +694,13 @@ public class RealmEntity extends AbstractIdentifiableEntity { public void setComponentEntities(List componentEntities) { this.componentEntities = componentEntities; } + + public Map getAttributes() { + return attributes; + } + + public void setAttributes(Map attributes) { + this.attributes = attributes; + } + } - - diff --git a/server-spi/src/main/java/org/keycloak/models/utils/DefaultAuthenticationFlows.java b/server-spi/src/main/java/org/keycloak/models/utils/DefaultAuthenticationFlows.java index 7b2c61ba93..a738d05af7 100755 --- a/server-spi/src/main/java/org/keycloak/models/utils/DefaultAuthenticationFlows.java +++ b/server-spi/src/main/java/org/keycloak/models/utils/DefaultAuthenticationFlows.java @@ -276,6 +276,7 @@ public class DefaultAuthenticationFlows { execution.setAuthenticatorFlow(false); realm.addAuthenticatorExecution(execution); + addIdentityProviderAuthenticator(realm, null); AuthenticationFlowModel forms = new AuthenticationFlowModel(); forms.setTopLevel(false); @@ -317,6 +318,45 @@ public class DefaultAuthenticationFlows { realm.addAuthenticatorExecution(execution); } + public static void addIdentityProviderAuthenticator(RealmModel realm, String defaultProvider) { + String browserFlowId = null; + for (AuthenticationFlowModel f : realm.getAuthenticationFlows()) { + if (f.getAlias().equals(DefaultAuthenticationFlows.BROWSER_FLOW)) { + browserFlowId = f.getId(); + break; + } + } + + if (browserFlowId != null) { + for (AuthenticationExecutionModel e : realm.getAuthenticationExecutions(browserFlowId)) { + if ("identity-provider-redirector".equals(e.getAuthenticator())) { + return; + } + } + + AuthenticationExecutionModel execution; + execution = new AuthenticationExecutionModel(); + execution.setParentFlow(browserFlowId); + execution.setRequirement(AuthenticationExecutionModel.Requirement.ALTERNATIVE); + execution.setAuthenticator("identity-provider-redirector"); + execution.setPriority(25); + execution.setAuthenticatorFlow(false); + + if (defaultProvider != null) { + AuthenticatorConfigModel configModel = new AuthenticatorConfigModel(); + + Map config = new HashMap<>(); + config.put("defaultProvider", defaultProvider); + configModel.setConfig(config); + configModel = realm.addAuthenticatorConfig(configModel); + + execution.setAuthenticatorConfig(configModel.getId()); + } + + realm.addAuthenticatorExecution(execution); + } + } + public static void clientAuthFlow(RealmModel realm) { AuthenticationFlowModel clients = new AuthenticationFlowModel(); clients.setAlias(CLIENT_AUTHENTICATION_FLOW); diff --git a/server-spi/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java b/server-spi/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java index bc20d49042..b8adc6271d 100755 --- a/server-spi/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java +++ b/server-spi/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java @@ -44,8 +44,14 @@ import org.keycloak.models.UserModel; import org.keycloak.representations.idm.CertificateRepresentation; import org.keycloak.common.util.CertificateUtils; import org.keycloak.common.util.PemUtils; +import org.keycloak.transaction.JtaTransactionManagerLookup; import javax.crypto.spec.SecretKeySpec; +import javax.naming.InitialContext; +import javax.sql.DataSource; +import javax.transaction.InvalidTransactionException; +import javax.transaction.SystemException; +import javax.transaction.Transaction; import java.io.IOException; import java.io.StringWriter; import java.security.Key; @@ -56,6 +62,7 @@ import java.security.PrivateKey; import java.security.PublicKey; import java.security.SecureRandom; import java.security.cert.X509Certificate; +import java.sql.DriverManager; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; @@ -63,6 +70,7 @@ import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; +import java.util.function.Function; /** * Set of helper methods, which are useful in various model implementations. @@ -303,6 +311,7 @@ public final class KeycloakModelUtils { } } + public static String getMasterRealmAdminApplicationClientId(String realmName) { return realmName + "-realm"; } @@ -651,4 +660,33 @@ public final class KeycloakModelUtils { } } } + + public static void suspendJtaTransaction(KeycloakSessionFactory factory, Runnable runnable) { + JtaTransactionManagerLookup lookup = (JtaTransactionManagerLookup)factory.getProviderFactory(JtaTransactionManagerLookup.class); + Transaction suspended = null; + try { + if (lookup != null) { + if (lookup.getTransactionManager() != null) { + try { + suspended = lookup.getTransactionManager().suspend(); + } catch (SystemException e) { + throw new RuntimeException(e); + } + } + } + runnable.run(); + } finally { + if (suspended != null) { + try { + lookup.getTransactionManager().resume(suspended); + } catch (InvalidTransactionException e) { + throw new RuntimeException(e); + } catch (SystemException e) { + throw new RuntimeException(e); + } + } + + } + + } } diff --git a/server-spi/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java b/server-spi/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java index e2d4799206..2d75c547e6 100755 --- a/server-spi/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java +++ b/server-spi/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java @@ -390,6 +390,10 @@ public class ModelToRepresentation { exportRequiredActions(realm, rep); exportGroups(realm, rep); } + + Map attributes = realm.getAttributes(); + rep.setAttributes(attributes); + return rep; } diff --git a/server-spi/src/main/java/org/keycloak/models/utils/RepresentationToModel.java b/server-spi/src/main/java/org/keycloak/models/utils/RepresentationToModel.java index d1d5b059d7..8ea1e970d4 100755 --- a/server-spi/src/main/java/org/keycloak/models/utils/RepresentationToModel.java +++ b/server-spi/src/main/java/org/keycloak/models/utils/RepresentationToModel.java @@ -373,6 +373,15 @@ public class RepresentationToModel { if(rep.getDefaultLocale() != null){ newRealm.setDefaultLocale(rep.getDefaultLocale()); } + + // import attributes + + if (rep.getAttributes() != null) { + for (Map.Entry attr : rep.getAttributes().entrySet()) { + newRealm.setAttribute(attr.getKey(), attr.getValue()); + } + } + } protected static void importComponents(RealmModel newRealm, MultivaluedHashMap components, String parentId) { @@ -556,6 +565,19 @@ public class RepresentationToModel { if (newRealm.getFlowByAlias(DefaultAuthenticationFlows.FIRST_BROKER_LOGIN_FLOW) == null) { DefaultAuthenticationFlows.firstBrokerLoginFlow(newRealm, true); } + + // Added in 2.2 + String defaultProvider = null; + if (rep.getIdentityProviders() != null) { + for (IdentityProviderRepresentation i : rep.getIdentityProviders()) { + if (i.isEnabled() && i.isAuthenticateByDefault()) { + defaultProvider = i.getProviderId(); + break; + } + } + } + + DefaultAuthenticationFlows.addIdentityProviderAuthenticator(newRealm, defaultProvider); } private static void convertDeprecatedSocialProviders(RealmRepresentation rep) { @@ -719,6 +741,21 @@ public class RepresentationToModel { if (rep.getRealm() != null) { renameRealm(realm, rep.getRealm()); } + + // Import attributes first, so the stuff saved directly on representation (displayName, bruteForce etc) has bigger priority + if (rep.getAttributes() != null) { + Set attrsToRemove = new HashSet<>(realm.getAttributes().keySet()); + attrsToRemove.removeAll(rep.getAttributes().keySet()); + + for (Map.Entry entry : rep.getAttributes().entrySet()) { + realm.setAttribute(entry.getKey(), entry.getValue()); + } + + for (String attr : attrsToRemove) { + realm.removeAttribute(attr); + } + } + if (rep.getDisplayName() != null) realm.setDisplayName(rep.getDisplayName()); if (rep.getDisplayNameHtml() != null) realm.setDisplayNameHtml(rep.getDisplayNameHtml()); if (rep.isEnabled() != null) realm.setEnabled(rep.isEnabled()); @@ -783,7 +820,7 @@ public class RepresentationToModel { realm.setUserFederationProviders(providerModels); } - if ("GENERATE".equals(rep.getPublicKey())) { + if (Constants.GENERATE.equals(rep.getPublicKey())) { KeycloakModelUtils.generateRealmKeys(realm); } else { if (rep.getPrivateKey() != null && rep.getPublicKey() != null) { @@ -1004,6 +1041,8 @@ public class RepresentationToModel { client.updateDefaultRoles(resourceRep.getDefaultRoles()); } + + if (resourceRep.getProtocolMappers() != null) { // first, remove all default/built in mappers Set mappers = client.getProtocolMappers(); @@ -1012,6 +1051,9 @@ public class RepresentationToModel { for (ProtocolMapperRepresentation mapper : resourceRep.getProtocolMappers()) { client.addProtocolMapper(toModel(mapper)); } + + + } if (resourceRep.getClientTemplate() != null) { diff --git a/server-spi/src/main/java/org/keycloak/protocol/ProtocolMapper.java b/server-spi/src/main/java/org/keycloak/protocol/ProtocolMapper.java index 1942422269..8055fae82d 100755 --- a/server-spi/src/main/java/org/keycloak/protocol/ProtocolMapper.java +++ b/server-spi/src/main/java/org/keycloak/protocol/ProtocolMapper.java @@ -17,6 +17,10 @@ package org.keycloak.protocol; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.ProtocolMapperContainerModel; +import org.keycloak.models.ProtocolMapperModel; +import org.keycloak.models.RealmModel; import org.keycloak.provider.ConfiguredProvider; import org.keycloak.provider.Provider; import org.keycloak.provider.ProviderFactory; @@ -30,4 +34,16 @@ public interface ProtocolMapper extends Provider, ProviderFactoryMarek Posolda + */ +public class ProtocolMapperConfigException extends Exception { + + private String messageKey; + private Object[] parameters; + + public ProtocolMapperConfigException(String message) { + super(message); + } + + public ProtocolMapperConfigException(String message, String messageKey) { + super(message); + this.messageKey = messageKey; + } + + public ProtocolMapperConfigException(String message, Throwable cause) { + super(message, cause); + } + + public ProtocolMapperConfigException(String message, String messageKey, Throwable cause) { + super(message, cause); + this.messageKey = messageKey; + } + + public ProtocolMapperConfigException(String message, Object ... parameters) { + super(message); + this.parameters = parameters; + } + + public ProtocolMapperConfigException(String messageKey, String message, Object ... parameters) { + super(message); + this.messageKey = messageKey; + this.parameters = parameters; + } + + public String getMessageKey() { + return messageKey; + } + + public Object[] getParameters() { + return parameters; + } + + public void setParameters(Object[] parameters) { + this.parameters = parameters; + } + +} diff --git a/server-spi/src/main/java/org/keycloak/provider/EnvironmentDependentProviderFactory.java b/server-spi/src/main/java/org/keycloak/provider/EnvironmentDependentProviderFactory.java new file mode 100644 index 0000000000..b4e993a2c9 --- /dev/null +++ b/server-spi/src/main/java/org/keycloak/provider/EnvironmentDependentProviderFactory.java @@ -0,0 +1,33 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.keycloak.provider; + +/** + * Providers that are only supported in some environments can implement this interface to be able to determine if they + * should be available or not. + * + * @author Stian Thorgersen + */ +public interface EnvironmentDependentProviderFactory { + + /** + * @return true if the provider is supported and should be available, false otherwise + */ + boolean isSupported(); + +} diff --git a/server-spi/src/main/java/org/keycloak/storage/UserStorageProviderModel.java b/server-spi/src/main/java/org/keycloak/storage/UserStorageProviderModel.java index 351107f94f..42ad397ce0 100755 --- a/server-spi/src/main/java/org/keycloak/storage/UserStorageProviderModel.java +++ b/server-spi/src/main/java/org/keycloak/storage/UserStorageProviderModel.java @@ -17,14 +17,8 @@ package org.keycloak.storage; -import org.keycloak.common.util.MultivaluedHashMap; import org.keycloak.component.ComponentModel; -import org.keycloak.models.RealmModel; - -import java.io.Serializable; -import java.util.Comparator; -import java.util.HashMap; -import java.util.Map; +import org.keycloak.component.PrioritizedComponentModel; /** * Stored configuration of a User Storage provider instance. @@ -32,14 +26,7 @@ import java.util.Map; * @author Marek Posolda * @author Bill Burke */ -public class UserStorageProviderModel extends ComponentModel { - - public static Comparator comparator = new Comparator() { - @Override - public int compare(UserStorageProviderModel o1, UserStorageProviderModel o2) { - return o1.getPriority() - o2.getPriority(); - } - }; +public class UserStorageProviderModel extends PrioritizedComponentModel { public UserStorageProviderModel() { setProviderType(UserStorageProvider.class.getName()); @@ -49,14 +36,4 @@ public class UserStorageProviderModel extends ComponentModel { super(copy); } - public int getPriority() { - String priority = getConfig().getFirst("priority"); - if (priority == null) return 0; - return Integer.valueOf(priority); - - } - - public void setPriority(int priority) { - getConfig().putSingle("priority", Integer.toString(priority)); - } } diff --git a/server-spi/src/main/java/org/keycloak/storage/changeset/UserData.java b/server-spi/src/main/java/org/keycloak/storage/changeset/UserData.java deleted file mode 100755 index 4b2813a87a..0000000000 --- a/server-spi/src/main/java/org/keycloak/storage/changeset/UserData.java +++ /dev/null @@ -1,387 +0,0 @@ -/* - * Copyright 2016 Red Hat, Inc. and/or its affiliates - * and other contributors as indicated by the @author tags. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.keycloak.storage.changeset; - -import org.keycloak.common.util.MultivaluedHashMap; -import org.keycloak.models.UserCredentialValueModel; -import org.keycloak.models.entities.AbstractIdentifiableEntity; - -import java.util.Collections; -import java.util.HashSet; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; -import java.util.Set; - -/** - * @author Marek Posolda - */ -public class UserData { - - private String id; - private boolean idChanged; - private String username; - private boolean usernameChanged; - private Long createdTimestamp; - private boolean createdTimestampChanged; - private String firstName; - private boolean firstNameChanged; - private String lastName; - private boolean lastNameChanged; - private String email; - private boolean emailChanged; - private boolean emailVerified; - private boolean emailVerifiedChanged; - private boolean totp; - private boolean totpChanged; - private boolean enabled; - private boolean enabledChanged; - - private Set roleIds = new HashSet<>(); - private boolean rolesChanged; - private Set groupIds = new HashSet<>(); - private boolean groupsChanged; - - private MultivaluedHashMap attributes = new MultivaluedHashMap<>(); - private boolean attributesChanged; - private Set requiredActions = new HashSet<>(); - private boolean requiredActionsChanged; - private List credentials = new LinkedList<>(); - private boolean credentialsChanged; - - public void rememberState() { - original = new UserData(); - original.id = id; - original.username = username; - original.createdTimestamp = createdTimestamp; - original.firstName = firstName; - original.lastName = lastName; - original.email = email; - original.emailVerified = emailVerified; - original.totp = totp; - original.enabled = enabled; - original.attributes.putAll(attributes); - original.requiredActions.addAll(requiredActions); - original.credentials.addAll(credentials); - } - - private UserData original = null; - - public void clearChangeFlags() { - original = null; - idChanged = false; - usernameChanged = false; - createdTimestampChanged = false; - firstNameChanged = false; - lastNameChanged = false; - emailChanged = false; - emailVerifiedChanged = false; - totpChanged = false; - enabledChanged = false; - rolesChanged = false; - groupsChanged = false; - attributesChanged = false; - requiredActionsChanged = false; - credentialsChanged = false; - } - - public boolean isChanged() { - return !idChanged - && !usernameChanged - && !createdTimestampChanged - && !firstNameChanged - && !lastNameChanged - && !emailChanged - && !emailVerifiedChanged - && !totpChanged - && !enabledChanged - && !rolesChanged - && !groupsChanged - && !attributesChanged - && !requiredActionsChanged - && !credentialsChanged; - } - - public boolean isIdChanged() { - return idChanged; - } - - public boolean isUsernameChanged() { - return usernameChanged; - } - - public boolean isCreatedTimestampChanged() { - return createdTimestampChanged; - } - - public boolean isFirstNameChanged() { - return firstNameChanged; - } - - public boolean isLastNameChanged() { - return lastNameChanged; - } - - public boolean isEmailChanged() { - return emailChanged; - } - - public boolean isEmailVerifiedChanged() { - return emailVerifiedChanged; - } - - public boolean isTotpChanged() { - return totpChanged; - } - - public boolean isEnabledChanged() { - return enabledChanged; - } - - public boolean isRolesChanged() { - return rolesChanged; - } - - public boolean isGroupsChanged() { - return groupsChanged; - } - - public boolean isAttributesChanged() { - return attributesChanged; - } - - public boolean isRequiredActionsChanged() { - return requiredActionsChanged; - } - - public boolean isCredentialsChanged() { - return credentialsChanged; - } - - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id; - idChanged = true; - } - - public String getUsername() { - return username; - } - - public void setUsername(String username) { - this.username = username; - usernameChanged = true; - } - - public Long getCreatedTimestamp() { - return createdTimestamp; - } - - public void setCreatedTimestamp(Long timestamp) { - this.createdTimestamp = timestamp; - createdTimestampChanged = true; - } - - - public String getFirstName() { - return firstName; - } - - public void setFirstName(String firstName) { - this.firstName = firstName; - firstNameChanged = true; - } - - public String getLastName() { - return lastName; - } - - public void setLastName(String lastName) { - this.lastName = lastName; - lastNameChanged = true; - } - - public String getEmail() { - return email; - } - - public void setEmail(String email) { - this.email = email; - emailChanged = true; - } - - public boolean isEmailVerified() { - return emailVerified; - } - - public void setEmailVerified(boolean emailVerified) { - this.emailVerified = emailVerified; - emailVerifiedChanged = true; - } - - public boolean isTotp() { - return totp; - } - - public void setTotp(boolean totp) { - this.totp = totp; - totpChanged = true; - } - - public boolean isEnabled() { - return enabled; - } - - public void setEnabled(boolean enabled) { - this.enabled = enabled; - enabledChanged = true; - } - - public Set getRoleMappings() { - return Collections.unmodifiableSet(roleIds); - } - - public void grantRole(String roleId) { - if (roleIds.contains(roleId)) return; - roleIds.add(roleId); - rolesChanged = true; - } - - public void deleteRoleMapping(String roleId) { - if (!roleIds.contains(roleId)) return; - roleIds.remove(roleId); - rolesChanged = true; - } - - public MultivaluedHashMap getAttributes() { - return attributes; - } - - public void setSingleAttribute(String name, String value) { - attributes.putSingle(name, value); - attributesChanged = true; - - } - public void setAttribute(String name, List values) { - attributes.put(name, values); - attributesChanged = true; - } - public void removeAttribute(String name) { - attributes.remove(name); - attributesChanged = true; - } - - - - public Set getRequiredActions() { - return Collections.unmodifiableSet(requiredActions); - } - public void addRequiredAction(String action) { - if (requiredActions.contains(action)) return; - requiredActions.add(action); - requiredActionsChanged = true; - } - public void removeRequiredAction(String action) { - if (!requiredActions.contains(action)) return; - requiredActions.remove(action); - requiredActionsChanged = true; - } - - public List getCredentials() { - return Collections.unmodifiableList(credentials); - } - - public void removeCredentialType(String type) { - Iterator it = credentials.iterator(); - while (it.hasNext()) { - if (it.next().getType().equals(type)) { - it.remove(); - credentialsChanged = true; - } - } - - } - - public void removeCredentialDevice(String type, String device) { - Iterator it = credentials.iterator(); - while (it.hasNext()) { - UserCredentialValueModel next = it.next(); - if (next.getType().equals(type) && next.getDevice().equals(device)) { - it.remove(); - credentialsChanged = true; - } - } - - } - - public void setCredential(UserCredentialValueModel cred) { - removeCredentialType(cred.getType()); - addCredential(cred); - } - public void addCredential(UserCredentialValueModel cred) { - credentials.add(cred); - credentialsChanged = true; - } - - public Set getGroupIds() { - return Collections.unmodifiableSet(groupIds); - } - - public void joinGroup(String groupId) { - if (groupIds.contains(groupId)) return; - groupIds.add(groupId); - groupsChanged = true; - } - - public void leaveGroup(String groupId) { - if (!groupIds.contains(groupId)) return; - groupIds.remove(groupId); - groupsChanged = true; - } - - @Override - public boolean equals(Object o) { - if (o == this) return true; - - if (this.id == null) return false; - - if (o == null || getClass() != o.getClass()) return false; - - AbstractIdentifiableEntity that = (AbstractIdentifiableEntity) o; - - if (!getId().equals(that.getId())) return false; - - return true; - - } - - @Override - public int hashCode() { - return id!=null ? id.hashCode() : super.hashCode(); - } - - @Override - public String toString() { - return String.format("%s [ id=%s ]", getClass().getSimpleName(), getId()); - } - -} - diff --git a/server-spi/src/main/java/org/keycloak/storage/changeset/UserDataAdapter.java b/server-spi/src/main/java/org/keycloak/storage/changeset/UserDataAdapter.java deleted file mode 100644 index 6ec78f25b6..0000000000 --- a/server-spi/src/main/java/org/keycloak/storage/changeset/UserDataAdapter.java +++ /dev/null @@ -1,340 +0,0 @@ -/* - * Copyright 2016 Red Hat, Inc. and/or its affiliates - * and other contributors as indicated by the @author tags. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.keycloak.storage.changeset; - -import org.keycloak.common.util.Time; -import org.keycloak.hash.PasswordHashManager; -import org.keycloak.models.ClientModel; -import org.keycloak.models.GroupModel; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.OTPPolicy; -import org.keycloak.models.PasswordPolicy; -import org.keycloak.models.RealmModel; -import org.keycloak.models.RoleContainerModel; -import org.keycloak.models.RoleModel; -import org.keycloak.models.UserCredentialModel; -import org.keycloak.models.UserCredentialValueModel; -import org.keycloak.models.UserModel; -import org.keycloak.models.utils.KeycloakModelUtils; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashSet; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Set; - -/** - * - * - * @author Bill Burke - * @version $Revision: 1 $ - */ -public class UserDataAdapter implements UserModel { - protected UserData userData; - protected RealmModel realm; - protected KeycloakSession session; - protected Set managedCredentialTypes; - protected List updatedManagedCredentials = new LinkedList<>(); - - public UserDataAdapter(KeycloakSession session, RealmModel realm, UserData userData) { - this.session = session; - this.realm = realm; - this.userData = userData; - this.userData.rememberState(); - } - - @Override - public String getId() { - return userData.getId(); - } - - @Override - public String getUsername() { - return userData.getUsername(); - } - - @Override - public void setUsername(String username) { - userData.setUsername(username); - - } - - @Override - public Long getCreatedTimestamp() { - return userData.getCreatedTimestamp(); - } - - @Override - public void setCreatedTimestamp(Long timestamp) { - userData.setCreatedTimestamp(timestamp); - - } - - @Override - public boolean isEnabled() { - return userData.isEnabled(); - } - - @Override - public boolean isOtpEnabled() { - return userData.isTotp(); - } - - @Override - public void setEnabled(boolean enabled) { - userData.setEnabled(enabled); - - } - - @Override - public void setSingleAttribute(String name, String value) { - userData.setSingleAttribute(name, value); - - } - - @Override - public void setAttribute(String name, List values) { - userData.setAttribute(name, values); - - } - - @Override - public void removeAttribute(String name) { - userData.removeAttribute(name); - - } - - @Override - public String getFirstAttribute(String name) { - return userData.getAttributes().getFirst(name); - } - - @Override - public List getAttribute(String name) { - return userData.getAttributes().get(name); - } - - @Override - public Map> getAttributes() { - return userData.getAttributes(); - } - - @Override - public Set getRequiredActions() { - return userData.getRequiredActions(); - } - - @Override - public void addRequiredAction(String action) { - userData.addRequiredAction(action); - - } - - @Override - public void removeRequiredAction(String action) { - userData.removeRequiredAction(action); - - } - - @Override - public void addRequiredAction(RequiredAction action) { - userData.addRequiredAction(action.name()); - - } - - @Override - public void removeRequiredAction(RequiredAction action) { - userData.removeRequiredAction(action.name()); - - } - - @Override - public String getFirstName() { - return userData.getFirstName(); - } - - @Override - public void setFirstName(String firstName) { - userData.setFirstName(firstName); - - } - - @Override - public String getLastName() { - return userData.getLastName(); - } - - @Override - public void setLastName(String lastName) { - userData.setLastName(lastName); - - } - - @Override - public String getEmail() { - return userData.getEmail(); - } - - @Override - public void setEmail(String email) { - userData.setEmail(email); - - } - - @Override - public boolean isEmailVerified() { - return userData.isEmailVerified(); - } - - @Override - public void setEmailVerified(boolean verified) { - userData.setEmailVerified(verified); - - } - - @Override - public void setOtpEnabled(boolean totp) { - userData.setTotp(totp); - - } - - @Override - public void updateCredential(UserCredentialModel cred) { - - } - - @Override - public List getCredentialsDirectly() { - return null; - } - - @Override - public void updateCredentialDirectly(UserCredentialValueModel cred) { - - } - - @Override - public Set getGroups() { - Set groups = userData.getGroupIds(); - Set set = new HashSet<>(); - for (String id : groups) { - GroupModel group = realm.getGroupById(id); - if (group != null) set.add(group); - } - return set; - } - - @Override - public void joinGroup(GroupModel group) { - userData.joinGroup(group.getId()); - - } - - @Override - public void leaveGroup(GroupModel group) { - userData.leaveGroup(group.getId()); - - } - - @Override - public boolean isMemberOf(GroupModel group) { - Set roles = getGroups(); - return KeycloakModelUtils.isMember(roles, group); - } - - @Override - public String getFederationLink() { - return null; - } - - @Override - public void setFederationLink(String link) { - - } - - @Override - public String getServiceAccountClientLink() { - return null; - } - - @Override - public void setServiceAccountClientLink(String clientInternalId) { - - } - - @Override - public Set getRealmRoleMappings() { - Set roleMappings = getRoleMappings(); - - Set realmRoles = new HashSet(); - for (RoleModel role : roleMappings) { - RoleContainerModel container = role.getContainer(); - if (container instanceof RealmModel) { - realmRoles.add(role); - } - } - return realmRoles; - } - - @Override - public Set getClientRoleMappings(ClientModel app) { - Set roleMappings = getRoleMappings(); - - Set roles = new HashSet(); - for (RoleModel role : roleMappings) { - RoleContainerModel container = role.getContainer(); - if (container instanceof ClientModel) { - ClientModel appModel = (ClientModel)container; - if (appModel.getId().equals(app.getId())) { - roles.add(role); - } - } - } - return roles; - } - - @Override - public boolean hasRole(RoleModel role) { - Set roles = getRoleMappings(); - return KeycloakModelUtils.hasRole(roles, role); - } - - @Override - public void grantRole(RoleModel role) { - userData.grantRole(role.getId()); - - } - - @Override - public Set getRoleMappings() { - Set roles = userData.getRoleMappings(); - Set set = new HashSet<>(); - for (String id : roles) { - RoleModel role = realm.getRoleById(id); - if (role != null) set.add(role); - } - return set; - } - - @Override - public void deleteRoleMapping(RoleModel role) { - userData.deleteRoleMapping(role.getId()); - } -} diff --git a/server-spi/src/main/java/org/keycloak/storage/federated/UserCredentialsFederatedStorage.java b/server-spi/src/main/java/org/keycloak/storage/federated/UserCredentialsFederatedStorage.java index 7239182877..2202f628ef 100644 --- a/server-spi/src/main/java/org/keycloak/storage/federated/UserCredentialsFederatedStorage.java +++ b/server-spi/src/main/java/org/keycloak/storage/federated/UserCredentialsFederatedStorage.java @@ -16,6 +16,7 @@ */ package org.keycloak.storage.federated; +import org.keycloak.credential.CredentialModel; import org.keycloak.models.RealmModel; import org.keycloak.models.UserCredentialModel; import org.keycloak.models.UserCredentialValueModel; @@ -28,8 +29,20 @@ import java.util.List; * @version $Revision: 1 $ */ public interface UserCredentialsFederatedStorage { + // deprecated void updateCredential(RealmModel realm, UserModel user, UserCredentialModel cred); void updateCredential(RealmModel realm, UserModel user, UserCredentialValueModel cred); void removeCredential(RealmModel realm, UserModel user, UserCredentialValueModel cred); List getCredentials(RealmModel realm, UserModel user); + + // new + void updateCredential(RealmModel realm, UserModel user, CredentialModel cred); + CredentialModel createCredential(RealmModel realm, UserModel user, CredentialModel cred); + boolean removeCredential(RealmModel realm, String id); + CredentialModel getCredentialById(String id); + List getCredentials(RealmModel realm); + List getUserCredentials(RealmModel realm, UserModel user); + List getCredentialsByType(RealmModel realm, UserModel user, String type); + CredentialModel getCredentialByNameAndType(RealmModel realm, UserModel user, String name, String type); + } diff --git a/services/src/main/java/org/keycloak/transaction/JtaTransactionManagerLookup.java b/server-spi/src/main/java/org/keycloak/transaction/JtaTransactionManagerLookup.java similarity index 100% rename from services/src/main/java/org/keycloak/transaction/JtaTransactionManagerLookup.java rename to server-spi/src/main/java/org/keycloak/transaction/JtaTransactionManagerLookup.java diff --git a/services/src/main/java/org/keycloak/transaction/TransactionManagerLookupSpi.java b/server-spi/src/main/java/org/keycloak/transaction/TransactionManagerLookupSpi.java similarity index 100% rename from services/src/main/java/org/keycloak/transaction/TransactionManagerLookupSpi.java rename to server-spi/src/main/java/org/keycloak/transaction/TransactionManagerLookupSpi.java diff --git a/server-spi/src/main/resources/META-INF/services/org.keycloak.provider.Spi b/server-spi/src/main/resources/META-INF/services/org.keycloak.provider.Spi index d2431d84b0..5ab0346aa3 100755 --- a/server-spi/src/main/resources/META-INF/services/org.keycloak.provider.Spi +++ b/server-spi/src/main/resources/META-INF/services/org.keycloak.provider.Spi @@ -61,4 +61,5 @@ org.keycloak.authorization.AuthorizationSpi org.keycloak.models.cache.authorization.CachedStoreFactorySpi org.keycloak.protocol.oidc.TokenIntrospectionSpi org.keycloak.policy.PasswordPolicySpi -org.keycloak.policy.PasswordPolicyManagerSpi \ No newline at end of file +org.keycloak.policy.PasswordPolicyManagerSpi +org.keycloak.transaction.TransactionManagerLookupSpi diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpEmailVerificationAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpEmailVerificationAuthenticator.java index 620e0c9f87..e56e7e6e31 100755 --- a/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpEmailVerificationAuthenticator.java +++ b/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpEmailVerificationAuthenticator.java @@ -132,6 +132,9 @@ public class IdpEmailVerificationAuthenticator extends AbstractIdpAuthenticator clientSession.setNote(IS_DIFFERENT_BROWSER, "true"); } + // User successfully confirmed linking by email verification. His email was defacto verified + existingUser.setEmailVerified(true); + context.setUser(existingUser); context.success(); } else { diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/browser/IdentityProviderAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/browser/IdentityProviderAuthenticator.java new file mode 100644 index 0000000000..795d0c2d6e --- /dev/null +++ b/services/src/main/java/org/keycloak/authentication/authenticators/browser/IdentityProviderAuthenticator.java @@ -0,0 +1,103 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.keycloak.authentication.authenticators.browser; + +import org.jboss.logging.Logger; +import org.keycloak.authentication.AuthenticationFlowContext; +import org.keycloak.authentication.Authenticator; +import org.keycloak.constants.AdapterConstants; +import org.keycloak.models.IdentityProviderModel; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.RealmModel; +import org.keycloak.models.UserModel; +import org.keycloak.services.Urls; +import org.keycloak.services.managers.ClientSessionCode; + +import javax.ws.rs.core.Response; +import java.util.List; + +/** + * @author Stian Thorgersen + */ +public class IdentityProviderAuthenticator implements Authenticator { + + private static final Logger LOG = Logger.getLogger(IdentityProviderAuthenticator.class); + + @Override + public void authenticate(AuthenticationFlowContext context) { + if (context.getUriInfo().getQueryParameters().containsKey(AdapterConstants.KC_IDP_HINT)) { + String providerId = context.getUriInfo().getQueryParameters().getFirst(AdapterConstants.KC_IDP_HINT); + if (providerId == null || providerId.equals("")) { + LOG.tracef("Skipping: kc_idp_hint query parameter is empty"); + context.attempted(); + } else { + LOG.tracef("Redirecting: %s set to %s", AdapterConstants.KC_IDP_HINT, providerId); + redirect(context, providerId); + } + } else if (context.getAuthenticatorConfig() != null && context.getAuthenticatorConfig().getConfig().containsKey(IdentityProviderAuthenticatorFactory.DEFAULT_PROVIDER)) { + String defaultProvider = context.getAuthenticatorConfig().getConfig().get(IdentityProviderAuthenticatorFactory.DEFAULT_PROVIDER); + LOG.tracef("Redirecting: default provider set to %s", defaultProvider); + redirect(context, defaultProvider); + } else { + LOG.tracef("No default provider set or %s query parameter provided", AdapterConstants.KC_IDP_HINT); + context.attempted(); + } + } + + private void redirect(AuthenticationFlowContext context, String providerId) { + List identityProviders = context.getRealm().getIdentityProviders(); + for (IdentityProviderModel identityProvider : identityProviders) { + if (identityProvider.isEnabled() && providerId.equals(identityProvider.getAlias())) { + String accessCode = new ClientSessionCode(context.getRealm(), context.getClientSession()).getCode(); + Response response = Response.temporaryRedirect( + Urls.identityProviderAuthnRequest(context.getUriInfo().getBaseUri(), providerId, context.getRealm().getName(), accessCode)) + .build(); + + LOG.debugf("Redirecting to %s", providerId); + context.forceChallenge(response); + return; + } + } + + LOG.warnf("Provider not found or not enabled for realm %s", providerId); + context.attempted(); + } + + @Override + public void action(AuthenticationFlowContext context) { + } + + @Override + public boolean requiresUser() { + return false; + } + + @Override + public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) { + return true; + } + + @Override + public void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user) { + } + + @Override + public void close() { + } + +} diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/browser/IdentityProviderAuthenticatorFactory.java b/services/src/main/java/org/keycloak/authentication/authenticators/browser/IdentityProviderAuthenticatorFactory.java new file mode 100644 index 0000000000..635c95e3d7 --- /dev/null +++ b/services/src/main/java/org/keycloak/authentication/authenticators/browser/IdentityProviderAuthenticatorFactory.java @@ -0,0 +1,102 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.keycloak.authentication.authenticators.browser; + +import org.keycloak.Config; +import org.keycloak.authentication.Authenticator; +import org.keycloak.authentication.AuthenticatorFactory; +import org.keycloak.models.AuthenticationExecutionModel; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.KeycloakSessionFactory; +import org.keycloak.provider.ProviderConfigProperty; + +import java.util.Collections; +import java.util.List; + +import static org.keycloak.provider.ProviderConfigProperty.STRING_TYPE; + +/** + * @author Stian Thorgersen + */ +public class IdentityProviderAuthenticatorFactory implements AuthenticatorFactory { + + protected static final AuthenticationExecutionModel.Requirement[] REQUIREMENT_CHOICES = { + AuthenticationExecutionModel.Requirement.ALTERNATIVE, AuthenticationExecutionModel.Requirement.DISABLED + }; + + protected static final String DEFAULT_PROVIDER = "defaultProvider"; + + @Override + public String getDisplayType() { + return "Identity Provider Redirector"; + } + + @Override + public String getReferenceCategory() { + return null; + } + + @Override + public boolean isConfigurable() { + return true; + } + + @Override + public AuthenticationExecutionModel.Requirement[] getRequirementChoices() { + return REQUIREMENT_CHOICES; + } + + @Override + public boolean isUserSetupAllowed() { + return true; + } + + @Override + public String getHelpText() { + return "Redirects to default Identity Provider or Identity Provider specified with kc_idp_hint query parameter"; + } + + @Override + public List getConfigProperties() { + ProviderConfigProperty rep = new ProviderConfigProperty(DEFAULT_PROVIDER, "Default Identity Provider", "To automatically redirect to an identity provider set to the alias of the identity provider", STRING_TYPE, null); + return Collections.singletonList(rep); + } + + @Override + public Authenticator create(KeycloakSession session) { + return new IdentityProviderAuthenticator(); + } + + @Override + public void init(Config.Scope config) { + } + + @Override + public void postInit(KeycloakSessionFactory factory) { + } + + @Override + public void close() { + } + + @Override + public String getId() { + return "identity-provider-redirector"; + } + +} diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/client/JWTClientAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/client/JWTClientAuthenticator.java index 789d38111c..86dd4e8c74 100644 --- a/services/src/main/java/org/keycloak/authentication/authenticators/client/JWTClientAuthenticator.java +++ b/services/src/main/java/org/keycloak/authentication/authenticators/client/JWTClientAuthenticator.java @@ -40,6 +40,7 @@ import org.keycloak.jose.jws.JWSInput; import org.keycloak.jose.jws.crypto.RSAProvider; import org.keycloak.models.AuthenticationExecutionModel; import org.keycloak.models.ClientModel; +import org.keycloak.models.ModelException; import org.keycloak.models.RealmModel; import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.protocol.oidc.OIDCLoginProtocol; @@ -166,30 +167,13 @@ public class JWTClientAuthenticator extends AbstractClientAuthenticator { } protected PublicKey getSignatureValidationKey(ClientModel client, ClientAuthenticationFlowContext context) { - CertificateRepresentation certInfo = CertificateInfoHelper.getCertificateFromClient(client, ATTR_PREFIX); - - String encodedCertificate = certInfo.getCertificate(); - String encodedPublicKey = certInfo.getPublicKey(); - - if (encodedCertificate == null && encodedPublicKey == null) { - Response challengeResponse = ClientAuthUtil.errorResponse(Response.Status.BAD_REQUEST.getStatusCode(), "unauthorized_client", "Client '" + client.getClientId() + "' doesn't have certificate or publicKey configured"); + try { + return CertificateInfoHelper.getSignatureValidationKey(client, ATTR_PREFIX); + } catch (ModelException me) { + Response challengeResponse = ClientAuthUtil.errorResponse(Response.Status.BAD_REQUEST.getStatusCode(), "unauthorized_client", me.getMessage()); context.failure(AuthenticationFlowError.CLIENT_CREDENTIALS_SETUP_REQUIRED, challengeResponse); return null; } - - if (encodedCertificate != null && encodedPublicKey != null) { - Response challengeResponse = ClientAuthUtil.errorResponse(Response.Status.BAD_REQUEST.getStatusCode(), "unauthorized_client", "Client '" + client.getClientId() + "' has both publicKey and certificate configured"); - context.failure(AuthenticationFlowError.CLIENT_CREDENTIALS_SETUP_REQUIRED, challengeResponse); - return null; - } - - // TODO: Caching of publicKeys / certificates, so it doesn't need to be always computed from pem. For performance reasons... - if (encodedCertificate != null) { - X509Certificate clientCert = KeycloakModelUtils.getCertificate(encodedCertificate); - return clientCert.getPublicKey(); - } else { - return KeycloakModelUtils.getPublicKey(encodedPublicKey); - } } @Override diff --git a/services/src/main/java/org/keycloak/authorization/admin/PolicyEvaluationService.java b/services/src/main/java/org/keycloak/authorization/admin/PolicyEvaluationService.java index 4af34adf60..076f9af628 100644 --- a/services/src/main/java/org/keycloak/authorization/admin/PolicyEvaluationService.java +++ b/services/src/main/java/org/keycloak/authorization/admin/PolicyEvaluationService.java @@ -47,6 +47,7 @@ import org.keycloak.protocol.ProtocolMapper; import org.keycloak.protocol.oidc.mappers.OIDCAccessTokenMapper; import org.keycloak.representations.AccessToken; import org.keycloak.services.Urls; +import org.keycloak.services.resources.admin.RealmAuth; import javax.ws.rs.Consumes; import javax.ws.rs.POST; @@ -74,20 +75,23 @@ import static java.util.Arrays.asList; public class PolicyEvaluationService { private final AuthorizationProvider authorization; + private final RealmAuth auth; @Context private HttpRequest httpRequest; private final ResourceServer resourceServer; - PolicyEvaluationService(ResourceServer resourceServer, AuthorizationProvider authorization) { + PolicyEvaluationService(ResourceServer resourceServer, AuthorizationProvider authorization, RealmAuth auth) { this.resourceServer = resourceServer; this.authorization = authorization; + this.auth = auth; } @POST @Consumes("application/json") @Produces("application/json") public void evaluate(PolicyEvaluationRequest evaluationRequest, @Suspended AsyncResponse asyncResponse) { + this.auth.requireView(); KeycloakIdentity identity = createIdentity(evaluationRequest); EvaluationContext evaluationContext = createEvaluationContext(evaluationRequest, identity); authorization.evaluators().from(createPermissions(evaluationRequest, evaluationContext, authorization), evaluationContext).evaluate(createDecisionCollector(authorization, identity, asyncResponse)); @@ -167,6 +171,13 @@ public class PolicyEvaluationService { collect.addAll(storeFactory.getResourceStore().findByScope(scope.getId()).stream().map(resource12 -> new ResourcePermission(resource12, asList(scope), resourceServer)).collect(Collectors.toList())); } + collect.addAll(storeFactory.getResourceStore().findByResourceServer(resourceServer.getId()).stream().map(new Function() { + @Override + public ResourcePermission apply(Resource resource) { + return new ResourcePermission(resource, resource.getScopes(), resourceServer); + } + }).collect(Collectors.toList())); + return collect.stream(); } }).collect(Collectors.toList()); diff --git a/services/src/main/java/org/keycloak/authorization/admin/PolicyService.java b/services/src/main/java/org/keycloak/authorization/admin/PolicyService.java index 3b9c1942c5..b179378d70 100644 --- a/services/src/main/java/org/keycloak/authorization/admin/PolicyService.java +++ b/services/src/main/java/org/keycloak/authorization/admin/PolicyService.java @@ -273,7 +273,7 @@ public class PolicyService { @Path("evaluate") public PolicyEvaluationService getPolicyEvaluateResource() { this.auth.requireView(); - PolicyEvaluationService resource = new PolicyEvaluationService(this.resourceServer, this.authorization); + PolicyEvaluationService resource = new PolicyEvaluationService(this.resourceServer, this.authorization, this.auth); ResteasyProviderFactory.getInstance().injectProperties(resource); diff --git a/services/src/main/java/org/keycloak/authorization/admin/ResourceSetService.java b/services/src/main/java/org/keycloak/authorization/admin/ResourceSetService.java index d31146f7d4..64ef0619c1 100644 --- a/services/src/main/java/org/keycloak/authorization/admin/ResourceSetService.java +++ b/services/src/main/java/org/keycloak/authorization/admin/ResourceSetService.java @@ -177,7 +177,7 @@ public class ResourceSetService { @GET @NoCache @Produces("application/json") - public Response findAll(@QueryParam("name") String name, + public Response find(@QueryParam("name") String name, @QueryParam("uri") String uri, @QueryParam("owner") String owner, @QueryParam("type") String type, diff --git a/services/src/main/java/org/keycloak/authorization/admin/ScopeService.java b/services/src/main/java/org/keycloak/authorization/admin/ScopeService.java index 44464b4fe9..f050f324f1 100644 --- a/services/src/main/java/org/keycloak/authorization/admin/ScopeService.java +++ b/services/src/main/java/org/keycloak/authorization/admin/ScopeService.java @@ -107,6 +107,11 @@ public class ScopeService { } Scope scope = storeFactory.getScopeStore().findById(id); + + if (scope == null) { + return Response.status(Status.NOT_FOUND).build(); + } + PolicyStore policyStore = storeFactory.getPolicyStore(); List policies = policyStore.findByScopeIds(Arrays.asList(scope.getId()), resourceServer.getId()); diff --git a/services/src/main/java/org/keycloak/authorization/admin/representation/PolicyEvaluationResponse.java b/services/src/main/java/org/keycloak/authorization/admin/representation/PolicyEvaluationResponse.java index d320a644f9..4f0c64a8a9 100644 --- a/services/src/main/java/org/keycloak/authorization/admin/representation/PolicyEvaluationResponse.java +++ b/services/src/main/java/org/keycloak/authorization/admin/representation/PolicyEvaluationResponse.java @@ -132,7 +132,10 @@ public class PolicyEvaluationResponse { scopes.add(scope); } if (evaluationResultRepresentation.getStatus().equals(Effect.PERMIT)) { - result.getAllowedScopes().add(scope); + List allowedScopes = result.getAllowedScopes(); + if (!allowedScopes.contains(scope)) { + allowedScopes.add(scope); + } } } } diff --git a/services/src/main/java/org/keycloak/authorization/protection/resource/ResourceService.java b/services/src/main/java/org/keycloak/authorization/protection/resource/ResourceService.java index b02a935238..fdaa12f951 100644 --- a/services/src/main/java/org/keycloak/authorization/protection/resource/ResourceService.java +++ b/services/src/main/java/org/keycloak/authorization/protection/resource/ResourceService.java @@ -109,7 +109,7 @@ public class ResourceService { } private Set findAll() { - Response response = this.resourceManager.findAll(null, null, null, null, null, -1, -1); + Response response = this.resourceManager.find(null, null, null, null, null, -1, -1); List resources = (List) response.getEntity(); return resources.stream().map(ResourceRepresentation::getId).collect(Collectors.toSet()); } diff --git a/services/src/main/java/org/keycloak/broker/oidc/KeycloakOIDCIdentityProviderFactory.java b/services/src/main/java/org/keycloak/broker/oidc/KeycloakOIDCIdentityProviderFactory.java index d3e8eba5aa..56f24a2c58 100755 --- a/services/src/main/java/org/keycloak/broker/oidc/KeycloakOIDCIdentityProviderFactory.java +++ b/services/src/main/java/org/keycloak/broker/oidc/KeycloakOIDCIdentityProviderFactory.java @@ -18,6 +18,7 @@ package org.keycloak.broker.oidc; import org.keycloak.broker.provider.AbstractIdentityProviderFactory; import org.keycloak.models.IdentityProviderModel; +import org.keycloak.models.KeycloakSession; import java.io.InputStream; import java.util.Map; @@ -45,8 +46,8 @@ public class KeycloakOIDCIdentityProviderFactory extends AbstractIdentityProvide } @Override - public Map parseConfig(InputStream inputStream) { - return OIDCIdentityProviderFactory.parseOIDCConfig(inputStream); + public Map parseConfig(KeycloakSession session, InputStream inputStream) { + return OIDCIdentityProviderFactory.parseOIDCConfig(session, inputStream); } diff --git a/services/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProviderFactory.java b/services/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProviderFactory.java index 82c2cdd966..a0e5017504 100755 --- a/services/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProviderFactory.java +++ b/services/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProviderFactory.java @@ -19,6 +19,7 @@ package org.keycloak.broker.oidc; import org.keycloak.broker.provider.AbstractIdentityProviderFactory; import org.keycloak.jose.jwk.JWK; import org.keycloak.models.IdentityProviderModel; +import org.keycloak.models.KeycloakSession; import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.jose.jwk.JSONWebKeySet; import org.keycloak.protocol.oidc.representations.OIDCConfigurationRepresentation; @@ -56,11 +57,11 @@ public class OIDCIdentityProviderFactory extends AbstractIdentityProviderFactory } @Override - public Map parseConfig(InputStream inputStream) { - return parseOIDCConfig(inputStream); + public Map parseConfig(KeycloakSession session, InputStream inputStream) { + return parseOIDCConfig(session, inputStream); } - protected static Map parseOIDCConfig(InputStream inputStream) { + protected static Map parseOIDCConfig(KeycloakSession session, InputStream inputStream) { OIDCConfigurationRepresentation rep; try { rep = JsonSerialization.readValue(inputStream, OIDCConfigurationRepresentation.class); @@ -74,14 +75,14 @@ public class OIDCIdentityProviderFactory extends AbstractIdentityProviderFactory config.setTokenUrl(rep.getTokenEndpoint()); config.setUserInfoUrl(rep.getUserinfoEndpoint()); if (rep.getJwksUri() != null) { - sendJwksRequest(rep, config); + sendJwksRequest(session, rep, config); } return config.getConfig(); } - protected static void sendJwksRequest(OIDCConfigurationRepresentation rep, OIDCIdentityProviderConfig config) { + protected static void sendJwksRequest(KeycloakSession session, OIDCConfigurationRepresentation rep, OIDCIdentityProviderConfig config) { try { - JSONWebKeySet keySet = JWKSUtils.sendJwksRequest(rep.getJwksUri()); + JSONWebKeySet keySet = JWKSUtils.sendJwksRequest(session, rep.getJwksUri()); PublicKey key = JWKSUtils.getKeyForUse(keySet, JWK.Use.SIG); if (key == null) { logger.supportedJwkNotFound(JWK.Use.SIG.asString()); diff --git a/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProviderFactory.java b/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProviderFactory.java index dac6750719..261799524e 100755 --- a/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProviderFactory.java +++ b/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProviderFactory.java @@ -24,6 +24,7 @@ import org.keycloak.dom.saml.v2.metadata.IDPSSODescriptorType; import org.keycloak.dom.saml.v2.metadata.KeyDescriptorType; import org.keycloak.dom.saml.v2.metadata.KeyTypes; import org.keycloak.models.IdentityProviderModel; +import org.keycloak.models.KeycloakSession; import org.keycloak.saml.common.constants.JBossSAMLURIConstants; import org.keycloak.saml.common.exceptions.ParsingException; import org.keycloak.saml.common.util.DocumentUtil; @@ -54,7 +55,7 @@ public class SAMLIdentityProviderFactory extends AbstractIdentityProviderFactory } @Override - public Map parseConfig(InputStream inputStream) { + public Map parseConfig(KeycloakSession session, InputStream inputStream) { try { Object parsedObject = new SAMLParser().parse(inputStream); EntityDescriptorType entityType; diff --git a/services/src/main/java/org/keycloak/credential/LocalOTPCredentialManager.java b/services/src/main/java/org/keycloak/credential/LocalOTPCredentialManager.java new file mode 100644 index 0000000000..6d9baf0345 --- /dev/null +++ b/services/src/main/java/org/keycloak/credential/LocalOTPCredentialManager.java @@ -0,0 +1,224 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.keycloak.credential; + +import org.jboss.logging.Logger; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.OTPPolicy; +import org.keycloak.models.RealmModel; +import org.keycloak.models.UserCredentialModel; +import org.keycloak.models.UserModel; +import org.keycloak.models.cache.CachedUserModel; +import org.keycloak.models.cache.OnUserCache; +import org.keycloak.models.utils.HmacOTP; +import org.keycloak.models.utils.TimeBasedOTP; + +import java.util.Collections; +import java.util.List; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class LocalOTPCredentialManager implements CredentialInputValidator, CredentialInputUpdater, OnUserCache { + private static final Logger logger = Logger.getLogger(LocalOTPCredentialManager.class); + + protected KeycloakSession session; + + protected List getCachedCredentials(UserModel user, String type) { + if (!(user instanceof CachedUserModel)) return Collections.EMPTY_LIST; + CachedUserModel cached = (CachedUserModel)user; + List rtn = (List)cached.getCachedWith().get(LocalOTPCredentialManager.class.getName() + "." + type); + if (rtn == null) return Collections.EMPTY_LIST; + return rtn; + } + + protected UserCredentialStore getCredentialStore() { + return session.userCredentialManager(); + } + + @Override + public void onCache(RealmModel realm, CachedUserModel user) { + List creds = getCredentialStore().getStoredCredentialsByType(realm, user, CredentialModel.TOTP); + user.getCachedWith().put(LocalOTPCredentialManager.class.getName() + "." + CredentialModel.TOTP, creds); + + } + + public LocalOTPCredentialManager(KeycloakSession session) { + this.session = session; + } + + @Override + public boolean updateCredential(RealmModel realm, UserModel user, CredentialInput input) { + if (!supportsCredentialType(input.getType())) return false; + + if (!(input instanceof UserCredentialModel)) { + logger.debug("Expected instance of UserCredentialModel for CredentialInput"); + return false; + } + UserCredentialModel inputModel = (UserCredentialModel)input; + CredentialModel model = null; + if (inputModel.getDevice() != null) { + model = getCredentialStore().getStoredCredentialByNameAndType(realm, user, inputModel.getDevice(), CredentialModel.TOTP); + if (model == null) { + model = getCredentialStore().getStoredCredentialByNameAndType(realm, user, inputModel.getDevice(), CredentialModel.HOTP); + } + } + if (model == null) { + // delete all existing + disableCredentialType(realm, user, CredentialModel.OTP); + model = new CredentialModel(); + } + + OTPPolicy policy = realm.getOTPPolicy(); + model.setDigits(policy.getDigits()); + model.setCounter(policy.getInitialCounter()); + model.setAlgorithm(policy.getAlgorithm()); + model.setType(policy.getType()); + model.setValue(inputModel.getValue()); + model.setDevice(inputModel.getDevice()); + model.setPeriod(policy.getPeriod()); + if (model.getId() == null) { + getCredentialStore().createCredential(realm, user, model); + } else { + getCredentialStore().updateCredential(realm, user, model); + } + session.getUserCache().evict(realm, user); + return true; + + + + } + + @Override + public void disableCredentialType(RealmModel realm, UserModel user, String credentialType) { + boolean disableTOTP = false, disableHOTP = false; + if (CredentialModel.OTP.equals(credentialType)) { + disableTOTP = true; + disableHOTP = true; + } else if (CredentialModel.HOTP.equals(credentialType)) { + disableHOTP = true; + + } else if (CredentialModel.TOTP.equals(credentialType)) { + disableTOTP = true; + } + if (disableHOTP) { + List hotp = getCredentialStore().getStoredCredentialsByType(realm, user, CredentialModel.HOTP); + for (CredentialModel cred : hotp) { + getCredentialStore().removeStoredCredential(realm, user, cred.getId()); + } + + } + if (disableTOTP) { + List totp = getCredentialStore().getStoredCredentialsByType(realm, user, CredentialModel.TOTP); + if (!totp.isEmpty()) { + for (CredentialModel cred : totp) { + getCredentialStore().removeStoredCredential(realm, user, cred.getId()); + } + } + + } + if (disableTOTP || disableHOTP) { + session.getUserCache().evict(realm, user); + } + } + + @Override + public boolean supportsCredentialType(String credentialType) { + return CredentialModel.OTP.equals(credentialType) + || CredentialModel.HOTP.equals(credentialType) + || CredentialModel.TOTP.equals(credentialType); + } + + @Override + public boolean isConfiguredFor(RealmModel realm, UserModel user, String credentialType) { + if (!supportsCredentialType(credentialType)) return false; + if (CredentialModel.OTP.equals(credentialType)) { + if (realm.getOTPPolicy().getType().equals(CredentialModel.HOTP)) { + return configuredForHOTP(realm, user); + } else { + return configuredForTOTP(realm, user); + } + } else if (CredentialModel.HOTP.equals(credentialType)) { + return configuredForHOTP(realm, user); + + } else if (CredentialModel.TOTP.equals(credentialType)) { + return configuredForTOTP(realm, user); + } else { + return false; + } + + } + + protected boolean configuredForHOTP(RealmModel realm, UserModel user) { + return !getCredentialStore().getStoredCredentialsByType(realm, user, CredentialModel.HOTP).isEmpty(); + } + + protected boolean configuredForTOTP(RealmModel realm, UserModel user) { + return !getCachedCredentials(user, CredentialModel.TOTP).isEmpty() + || !getCredentialStore().getStoredCredentialsByType(realm, user, CredentialModel.TOTP).isEmpty(); + } + + public static boolean validOTP(RealmModel realm, String token, String secret) { + OTPPolicy policy = realm.getOTPPolicy(); + if (policy.getType().equals(UserCredentialModel.TOTP)) { + TimeBasedOTP validator = new TimeBasedOTP(policy.getAlgorithm(), policy.getDigits(), policy.getPeriod(), policy.getLookAheadWindow()); + return validator.validateTOTP(token, secret.getBytes()); + } else { + HmacOTP validator = new HmacOTP(policy.getDigits(), policy.getAlgorithm(), policy.getLookAheadWindow()); + int c = validator.validateHOTP(token, secret, policy.getInitialCounter()); + return c > -1; + } + + } + + @Override + public boolean isValid(RealmModel realm, UserModel user, CredentialInput input) { + if (! (input instanceof UserCredentialModel)) { + logger.debug("Expected instance of UserCredentialModel for CredentialInput"); + return false; + + } + String token = ((UserCredentialModel)input).getValue(); + OTPPolicy policy = realm.getOTPPolicy(); + if (realm.getOTPPolicy().getType().equals(CredentialModel.HOTP)) { + HmacOTP validator = new HmacOTP(policy.getDigits(), policy.getAlgorithm(), policy.getLookAheadWindow()); + for (CredentialModel cred : getCredentialStore().getStoredCredentialsByType(realm, user, CredentialModel.HOTP)) { + int counter = validator.validateHOTP(token, cred.getValue(), cred.getCounter()); + if (counter < 0) continue; + cred.setCounter(counter); + getCredentialStore().updateCredential(realm, user, cred); + return true; + } + } else { + TimeBasedOTP validator = new TimeBasedOTP(policy.getAlgorithm(), policy.getDigits(), policy.getPeriod(), policy.getLookAheadWindow()); + List creds = getCachedCredentials(user, CredentialModel.TOTP); + if (creds.isEmpty()) { + creds = getCredentialStore().getStoredCredentialsByType(realm, user, CredentialModel.TOTP); + } else { + logger.debugv("Cache hit for TOTP for user {0}", user.getUsername()); + } + for (CredentialModel cred : creds) { + if (validator.validateTOTP(token, cred.getValue().getBytes())) { + return true; + } + } + + } + return false; + } +} diff --git a/services/src/main/java/org/keycloak/credential/UserCredentialStoreManager.java b/services/src/main/java/org/keycloak/credential/UserCredentialStoreManager.java new file mode 100644 index 0000000000..5f67768378 --- /dev/null +++ b/services/src/main/java/org/keycloak/credential/UserCredentialStoreManager.java @@ -0,0 +1,249 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.keycloak.credential; + +import org.keycloak.common.util.reflections.Types; +import org.keycloak.component.ComponentModel; +import org.keycloak.component.PrioritizedComponentModel; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.RealmModel; +import org.keycloak.models.UserCredentialManager; +import org.keycloak.models.UserModel; +import org.keycloak.models.cache.CachedUserModel; +import org.keycloak.models.cache.OnUserCache; +import org.keycloak.storage.StorageId; +import org.keycloak.storage.UserStorageManager; +import org.keycloak.storage.UserStorageProvider; + +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class UserCredentialStoreManager implements UserCredentialManager, OnUserCache { + protected KeycloakSession session; + + public UserCredentialStoreManager(KeycloakSession session) { + this.session = session; + } + + protected UserCredentialStore getStoreForUser(UserModel user) { + if (StorageId.isLocalStorage(user)) { + return (UserCredentialStore)session.userLocalStorage(); + } else { + return (UserCredentialStore)session.userFederatedStorage(); + } + } + + @Override + public void updateCredential(RealmModel realm, UserModel user, CredentialModel cred) { + getStoreForUser(user).updateCredential(realm, user, cred); + + } + + @Override + public CredentialModel createCredential(RealmModel realm, UserModel user, CredentialModel cred) { + return getStoreForUser(user).createCredential(realm, user, cred); + } + + @Override + public boolean removeStoredCredential(RealmModel realm, UserModel user, String id) { + return getStoreForUser(user).removeStoredCredential(realm, user, id); + } + + @Override + public CredentialModel getStoredCredentialById(RealmModel realm, UserModel user, String id) { + return getStoreForUser(user).getStoredCredentialById(realm, user, id); + } + + @Override + public List getStoredCredentials(RealmModel realm, UserModel user) { + return getStoreForUser(user).getStoredCredentials(realm, user); + } + + @Override + public List getStoredCredentialsByType(RealmModel realm, UserModel user, String type) { + return getStoreForUser(user).getStoredCredentialsByType(realm, user, type); + } + + @Override + public CredentialModel getStoredCredentialByNameAndType(RealmModel realm, UserModel user, String name, String type) { + return getStoreForUser(user).getStoredCredentialByNameAndType(realm, user, name, type); + } + + + @Override + public boolean isValid(RealmModel realm, UserModel user, List inputs) { + + List toValidate = new LinkedList<>(); + toValidate.addAll(inputs); + if (!StorageId.isLocalStorage(user)) { + String providerId = StorageId.resolveProviderId(user); + UserStorageProvider provider = UserStorageManager.getStorageProvider(session, realm, providerId); + if (provider instanceof CredentialInputValidator) { + Iterator it = toValidate.iterator(); + while (it.hasNext()) { + CredentialInput input = it.next(); + CredentialInputValidator validator = (CredentialInputValidator)provider; + if (validator.supportsCredentialType(input.getType()) && validator.isValid(realm, user, input)) { + it.remove(); + } + } + } + } + + if (toValidate.isEmpty()) return true; + + List components = getCredentialProviderComponents(realm); + for (ComponentModel component : components) { + CredentialProviderFactory factory = (CredentialProviderFactory) session.getKeycloakSessionFactory().getProviderFactory(CredentialProvider.class, component.getProviderId()); + if (!Types.supports(CredentialInputValidator.class, factory, CredentialProviderFactory.class)) continue; + Iterator it = toValidate.iterator(); + while (it.hasNext()) { + CredentialInput input = it.next(); + CredentialInputValidator validator = (CredentialInputValidator)session.getAttribute(component.getId()); + if (validator == null) { + validator = (CredentialInputValidator)factory.create(session, component); + session.setAttribute(component.getId(), validator); + } + if (validator.supportsCredentialType(input.getType()) && validator.isValid(realm, user, input)) { + it.remove(); + } + } + } + + return toValidate.isEmpty(); + } + + protected List getCredentialProviderComponents(RealmModel realm) { + List components = realm.getComponents(realm.getId(), CredentialProvider.class.getName()); + if (components.isEmpty()) return components; + List copy = new LinkedList<>(); + copy.addAll(components); + Collections.sort(copy, PrioritizedComponentModel.comparator); + return copy; + } + + @Override + public void updateCredential(RealmModel realm, UserModel user, CredentialInput input) { + if (!StorageId.isLocalStorage(user)) { + String providerId = StorageId.resolveProviderId(user); + UserStorageProvider provider = UserStorageManager.getStorageProvider(session, realm, providerId); + if (provider instanceof CredentialInputUpdater) { + CredentialInputUpdater updater = (CredentialInputUpdater)provider; + if (updater.supportsCredentialType(input.getType())) { + if (updater.updateCredential(realm, user, input)) return; + } + } + } + + List components = getCredentialProviderComponents(realm); + for (ComponentModel component : components) { + CredentialProviderFactory factory = (CredentialProviderFactory) session.getKeycloakSessionFactory().getProviderFactory(CredentialProvider.class, component.getProviderId()); + if (!Types.supports(CredentialInputUpdater.class, factory, CredentialProviderFactory.class)) continue; + CredentialInputUpdater updater = (CredentialInputUpdater)session.getAttribute(component.getId()); + if (updater == null) { + updater = (CredentialInputUpdater)factory.create(session, component); + session.setAttribute(component.getId(), updater); + } + if (!updater.supportsCredentialType(input.getType())) continue; + if (updater.updateCredential(realm, user, input)) return; + } + + } + @Override + public void disableCredential(RealmModel realm, UserModel user, String credentialType) { + if (!StorageId.isLocalStorage(user)) { + String providerId = StorageId.resolveProviderId(user); + UserStorageProvider provider = UserStorageManager.getStorageProvider(session, realm, providerId); + if (provider instanceof CredentialInputUpdater) { + CredentialInputUpdater updater = (CredentialInputUpdater)provider; + if (updater.supportsCredentialType(credentialType)) { + updater.disableCredentialType(realm, user, credentialType); + } + } + } + + List components = getCredentialProviderComponents(realm); + for (ComponentModel component : components) { + CredentialProviderFactory factory = (CredentialProviderFactory) session.getKeycloakSessionFactory().getProviderFactory(CredentialProvider.class, component.getProviderId()); + if (!Types.supports(CredentialInputUpdater.class, factory, CredentialProviderFactory.class)) continue; + CredentialInputUpdater updater = (CredentialInputUpdater)session.getAttribute(component.getId()); + if (updater == null) { + updater = (CredentialInputUpdater)factory.create(session, component); + session.setAttribute(component.getId(), updater); + } + if (!updater.supportsCredentialType(credentialType)) continue; + updater.disableCredentialType(realm, user, credentialType); + } + + } + @Override + public boolean isConfiguredFor(RealmModel realm, UserModel user, String type) { + if (!StorageId.isLocalStorage(user)) { + String providerId = StorageId.resolveProviderId(user); + UserStorageProvider provider = UserStorageManager.getStorageProvider(session, realm, providerId); + if (provider instanceof CredentialInputValidator) { + CredentialInputValidator validator = (CredentialInputValidator)provider; + if (validator.supportsCredentialType(type) && validator.isConfiguredFor(realm, user, type)) { + return true; + } + } + } + + List components = getCredentialProviderComponents(realm); + for (ComponentModel component : components) { + CredentialProviderFactory factory = (CredentialProviderFactory) session.getKeycloakSessionFactory().getProviderFactory(CredentialProvider.class, component.getProviderId()); + if (!Types.supports(CredentialInputUpdater.class, factory, CredentialProviderFactory.class)) continue; + CredentialInputValidator validator = (CredentialInputValidator)session.getAttribute(component.getId()); + if (validator == null) { + validator = (CredentialInputValidator)factory.create(session, component); + session.setAttribute(component.getId(), validator); + } + if (validator.supportsCredentialType(type) && validator.isConfiguredFor(realm, user, type)) { + return true; + } + } + return false; + + } + + @Override + public void onCache(RealmModel realm, CachedUserModel user) { + List components = getCredentialProviderComponents(realm); + for (ComponentModel component : components) { + CredentialProviderFactory factory = (CredentialProviderFactory) session.getKeycloakSessionFactory().getProviderFactory(CredentialProvider.class, component.getProviderId()); + if (!Types.supports(OnUserCache.class, factory, CredentialProviderFactory.class)) continue; + OnUserCache validator = (OnUserCache)session.getAttribute(component.getId()); + if (validator == null) { + validator = (OnUserCache)factory.create(session, component); + session.setAttribute(component.getId(), validator); + } + validator.onCache(realm, user); + } + + } + + @Override + public void close() { + + } +} diff --git a/services/src/main/java/org/keycloak/forms/account/freemarker/model/AccountBean.java b/services/src/main/java/org/keycloak/forms/account/freemarker/model/AccountBean.java index acb1514388..ceaaf94449 100755 --- a/services/src/main/java/org/keycloak/forms/account/freemarker/model/AccountBean.java +++ b/services/src/main/java/org/keycloak/forms/account/freemarker/model/AccountBean.java @@ -18,6 +18,7 @@ package org.keycloak.forms.account.freemarker.model; import org.jboss.logging.Logger; +import org.keycloak.models.Constants; import org.keycloak.models.UserModel; import javax.ws.rs.core.MultivaluedMap; @@ -55,8 +56,8 @@ public class AccountBean { if (profileFormData != null) { for (String key : profileFormData.keySet()) { - if (key.startsWith("user.attributes.")) { - String attribute = key.substring("user.attributes.".length()); + if (key.startsWith(Constants.USER_ATTRIBUTES_PREFIX)) { + String attribute = key.substring(Constants.USER_ATTRIBUTES_PREFIX.length()); attributes.put(attribute, profileFormData.getFirst(key)); } } @@ -72,7 +73,11 @@ public class AccountBean { } public String getUsername() { - return profileFormData != null ? profileFormData.getFirst("username") : user.getUsername(); + if (profileFormData != null && profileFormData.containsKey("username")) { + return profileFormData.getFirst("username"); + } else { + return user.getUsername(); + } } public String getEmail() { diff --git a/services/src/main/java/org/keycloak/protocol/AuthorizationEndpointBase.java b/services/src/main/java/org/keycloak/protocol/AuthorizationEndpointBase.java index bfbf7a1468..12dba27e2c 100755 --- a/services/src/main/java/org/keycloak/protocol/AuthorizationEndpointBase.java +++ b/services/src/main/java/org/keycloak/protocol/AuthorizationEndpointBase.java @@ -17,8 +17,6 @@ package org.keycloak.protocol; -import java.util.List; - import javax.ws.rs.core.Context; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.Response; @@ -31,14 +29,11 @@ import org.keycloak.events.Details; import org.keycloak.events.EventBuilder; import org.keycloak.models.AuthenticationFlowModel; import org.keycloak.models.ClientSessionModel; -import org.keycloak.models.IdentityProviderModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; import org.keycloak.protocol.LoginProtocol.Error; import org.keycloak.services.ServicesLogger; -import org.keycloak.services.Urls; import org.keycloak.services.managers.AuthenticationManager; -import org.keycloak.services.managers.ClientSessionCode; import org.keycloak.services.resources.LoginActionsService; /** @@ -95,15 +90,6 @@ public abstract class AuthorizationEndpointBase { * @return response to be returned to the browser */ protected Response handleBrowserAuthenticationRequest(ClientSessionModel clientSession, LoginProtocol protocol, boolean isPassive, boolean redirectToAuthentication) { - - List identityProviders = realm.getIdentityProviders(); - for (IdentityProviderModel identityProvider : identityProviders) { - if (identityProvider.isEnabled() && identityProvider.isAuthenticateByDefault()) { - // TODO if we are isPassive we should propagate this flag to default identity provider also if possible - return buildRedirectToIdentityProvider(identityProvider.getAlias(), new ClientSessionCode(realm, clientSession).getCode()); - } - } - AuthenticationFlowModel flow = getAuthenticationFlow(); String flowId = flow.getId(); AuthenticationProcessor processor = createProcessor(clientSession, flowId, LoginActionsService.AUTHENTICATE_PATH); @@ -147,11 +133,4 @@ public abstract class AuthorizationEndpointBase { return realm.getBrowserFlow(); } - protected Response buildRedirectToIdentityProvider(String providerId, String accessCode) { - logger.debug("Automatically redirect to identity provider: " + providerId); - return Response.temporaryRedirect( - Urls.identityProviderAuthnRequest(this.uriInfo.getBaseUri(), providerId, this.realm.getName(), accessCode)) - .build(); - } - } \ No newline at end of file diff --git a/services/src/main/java/org/keycloak/protocol/oidc/OIDCAdvancedConfigWrapper.java b/services/src/main/java/org/keycloak/protocol/oidc/OIDCAdvancedConfigWrapper.java index 37fe2043d6..e16d7b8b6d 100644 --- a/services/src/main/java/org/keycloak/protocol/oidc/OIDCAdvancedConfigWrapper.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/OIDCAdvancedConfigWrapper.java @@ -17,12 +17,13 @@ package org.keycloak.protocol.oidc; -import java.util.HashMap; - import org.keycloak.jose.jws.Algorithm; import org.keycloak.models.ClientModel; +import org.keycloak.protocol.oidc.utils.SubjectType; import org.keycloak.representations.idm.ClientRepresentation; +import java.util.HashMap; + /** * @author Marek Posolda */ @@ -30,6 +31,13 @@ public class OIDCAdvancedConfigWrapper { private static final String USER_INFO_RESPONSE_SIGNATURE_ALG = "user.info.response.signature.alg"; + private static final String REQUEST_OBJECT_SIGNATURE_ALG = "request.object.signature.alg"; + + private static final String SUBJECT_TYPE = "oidc.subject_type"; + private static final String SECTOR_IDENTIFIER_URI = "oidc.sector_identifier_uri"; + private static final String PUBLIC = "public"; + private static final String PAIRWISE = "pairwise"; + private final ClientModel clientModel; private final ClientRepresentation clientRep; @@ -62,6 +70,37 @@ public class OIDCAdvancedConfigWrapper { return getUserInfoSignedResponseAlg() != null; } + public Algorithm getRequestObjectSignatureAlg() { + String alg = getAttribute(REQUEST_OBJECT_SIGNATURE_ALG); + return alg==null ? null : Enum.valueOf(Algorithm.class, alg); + } + + public void setRequestObjectSignatureAlg(Algorithm alg) { + String algStr = alg==null ? null : alg.toString(); + setAttribute(REQUEST_OBJECT_SIGNATURE_ALG, algStr); + } + + public void setSubjectType(SubjectType subjectType) { + if (subjectType == null) { + setAttribute(SUBJECT_TYPE, SubjectType.PUBLIC.toString()); + return; + } + setAttribute(SUBJECT_TYPE, subjectType.toString()); + } + + public SubjectType getSubjectType() { + String subjectType = getAttribute(SUBJECT_TYPE); + return subjectType == null ? SubjectType.PUBLIC : Enum.valueOf(SubjectType.class, subjectType); + } + + public void setSectorIdentifierUri(String sectorIdentifierUri) { + setAttribute(SECTOR_IDENTIFIER_URI, sectorIdentifierUri); + } + + public String getSectorIdentifierUri() { + return getAttribute(SECTOR_IDENTIFIER_URI); + } + private String getAttribute(String attrKey) { if (clientModel != null) { diff --git a/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocol.java b/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocol.java index 3c4c3aa650..c263405213 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocol.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocol.java @@ -66,6 +66,7 @@ public class OIDCLoginProtocol implements LoginProtocol { public static final String REQUEST_PARAM = "request"; public static final String REQUEST_URI_PARAM = "request_uri"; public static final String UI_LOCALES_PARAM = OAuth2Constants.UI_LOCALES_PARAM; + public static final String CLAIMS_PARAM = "claims"; public static final String LOGOUT_REDIRECT_URI = "OIDC_LOGOUT_REDIRECT_URI"; public static final String ISSUER = "iss"; diff --git a/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolFactory.java b/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolFactory.java index 087b1f6009..aa56dc7963 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolFactory.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolFactory.java @@ -19,31 +19,15 @@ package org.keycloak.protocol.oidc; import org.keycloak.common.constants.KerberosConstants; import org.keycloak.common.util.UriUtils; import org.keycloak.events.EventBuilder; -import org.keycloak.models.ClientModel; -import org.keycloak.models.ClientTemplateModel; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.ProtocolMapperModel; -import org.keycloak.models.RealmModel; +import org.keycloak.models.*; import org.keycloak.protocol.AbstractLoginProtocolFactory; import org.keycloak.protocol.LoginProtocol; -import org.keycloak.protocol.oidc.mappers.AddressMapper; -import org.keycloak.protocol.oidc.mappers.FullNameMapper; -import org.keycloak.protocol.oidc.mappers.OIDCAttributeMapperHelper; -import org.keycloak.protocol.oidc.mappers.UserPropertyMapper; -import org.keycloak.protocol.oidc.mappers.UserSessionNoteMapper; +import org.keycloak.protocol.oidc.mappers.*; import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.ClientTemplateRepresentation; import org.keycloak.services.ServicesLogger; -import org.keycloak.services.managers.AuthenticationManager; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import org.keycloak.protocol.oidc.mappers.UserAttributeMapper; +import java.util.*; /** * @author Bill Burke @@ -146,6 +130,9 @@ public class OIDCLoginProtocolFactory extends AbstractLoginProtocolFactory { ProtocolMapperModel address = AddressMapper.createAddressMapper(); builtins.add(address); + ProtocolMapperModel pairwise = SHA265PairwiseSubMapper.createPairwiseMapper(); + builtins.add(pairwise); + model = UserSessionNoteMapper.createClaimMapper(KerberosConstants.GSS_DELEGATION_CREDENTIAL_DISPLAY_NAME, KerberosConstants.GSS_DELEGATION_CREDENTIAL, KerberosConstants.GSS_DELEGATION_CREDENTIAL, "String", diff --git a/services/src/main/java/org/keycloak/protocol/oidc/OIDCWellKnownProvider.java b/services/src/main/java/org/keycloak/protocol/oidc/OIDCWellKnownProvider.java index dfca14434f..2653b9b247 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/OIDCWellKnownProvider.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/OIDCWellKnownProvider.java @@ -50,11 +50,13 @@ public class OIDCWellKnownProvider implements WellKnownProvider { public static final List DEFAULT_USER_INFO_SIGNING_ALG_VALUES_SUPPORTED = list(Algorithm.RS256.toString()); + public static final List DEFAULT_REQUEST_OBJECT_SIGNING_ALG_VALUES_SUPPORTED = list(Algorithm.none.toString(), Algorithm.RS256.toString()); + public static final List DEFAULT_GRANT_TYPES_SUPPORTED = list(OAuth2Constants.AUTHORIZATION_CODE, OAuth2Constants.IMPLICIT, OAuth2Constants.REFRESH_TOKEN, OAuth2Constants.PASSWORD, OAuth2Constants.CLIENT_CREDENTIALS); public static final List DEFAULT_RESPONSE_TYPES_SUPPORTED = list(OAuth2Constants.CODE, OIDCResponseType.NONE, OIDCResponseType.ID_TOKEN, OIDCResponseType.TOKEN, "id_token token", "code id_token", "code token", "code id_token token"); - public static final List DEFAULT_SUBJECT_TYPES_SUPPORTED = list("public"); + public static final List DEFAULT_SUBJECT_TYPES_SUPPORTED = list("public", "pairwise"); public static final List DEFAULT_RESPONSE_MODES_SUPPORTED = list("query", "fragment", "form_post"); @@ -93,6 +95,7 @@ public class OIDCWellKnownProvider implements WellKnownProvider { config.setIdTokenSigningAlgValuesSupported(DEFAULT_ID_TOKEN_SIGNING_ALG_VALUES_SUPPORTED); config.setUserInfoSigningAlgValuesSupported(DEFAULT_USER_INFO_SIGNING_ALG_VALUES_SUPPORTED); + config.setRequestObjectSigningAlgValuesSupported(DEFAULT_REQUEST_OBJECT_SIGNING_ALG_VALUES_SUPPORTED); config.setResponseTypesSupported(DEFAULT_RESPONSE_TYPES_SUPPORTED); config.setSubjectTypesSupported(DEFAULT_SUBJECT_TYPES_SUPPORTED); config.setResponseModesSupported(DEFAULT_RESPONSE_MODES_SUPPORTED); @@ -107,8 +110,8 @@ public class OIDCWellKnownProvider implements WellKnownProvider { config.setScopesSupported(SCOPES_SUPPORTED); - config.setRequestParameterSupported(false); - config.setRequestUriParameterSupported(false); + config.setRequestParameterSupported(true); + config.setRequestUriParameterSupported(true); return config; } diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java index 931b04cfba..3f18b27c3a 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java @@ -17,11 +17,6 @@ package org.keycloak.protocol.oidc.endpoints; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - import javax.ws.rs.GET; import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.Response; @@ -34,14 +29,14 @@ import org.keycloak.events.Details; import org.keycloak.events.Errors; import org.keycloak.events.EventBuilder; import org.keycloak.events.EventType; -import org.keycloak.forms.login.LoginFormsProvider; import org.keycloak.models.AuthenticationFlowModel; import org.keycloak.models.ClientModel; import org.keycloak.models.ClientSessionModel; -import org.keycloak.models.IdentityProviderModel; import org.keycloak.models.RealmModel; import org.keycloak.protocol.AuthorizationEndpointBase; import org.keycloak.protocol.oidc.OIDCLoginProtocol; +import org.keycloak.protocol.oidc.endpoints.request.AuthorizationEndpointRequest; +import org.keycloak.protocol.oidc.endpoints.request.AuthorizationEndpointRequestParserProcessor; import org.keycloak.protocol.oidc.utils.OIDCRedirectUriBuilder; import org.keycloak.protocol.oidc.utils.OIDCResponseMode; import org.keycloak.protocol.oidc.utils.OIDCResponseType; @@ -49,7 +44,6 @@ import org.keycloak.protocol.oidc.utils.RedirectUtils; import org.keycloak.services.ErrorPageException; import org.keycloak.services.ServicesLogger; import org.keycloak.services.Urls; -import org.keycloak.services.managers.ClientSessionCode; import org.keycloak.services.messages.Messages; import org.keycloak.services.resources.LoginActionsService; import org.keycloak.services.util.CacheControlUtil; @@ -67,43 +61,10 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase { /** * Prefix used to store additional HTTP GET params from original client request into {@link ClientSessionModel} note to be available later in Authenticators, RequiredActions etc. Prefix is used to * prevent collisions with internally used notes. - * + * * @see ClientSessionModel#getNote(String) - * @see #KNOWN_REQ_PARAMS - * @see #additionalReqParams */ public static final String CLIENT_SESSION_NOTE_ADDITIONAL_REQ_PARAMS_PREFIX = "client_request_param_"; - /** - * Max number of additional req params copied into client session note to prevent DoS attacks - * - * @see #additionalReqParams - */ - public static final int ADDITIONAL_REQ_PARAMS_MAX_MUMBER = 5; - /** - * Max size of additional req param value copied into client session note to prevent DoS attacks - params with longer value are ignored - * - * @see #additionalReqParams - */ - public static final int ADDITIONAL_REQ_PARAMS_MAX_SIZE = 200; - - /** Set of known protocol GET params not to be stored into {@link #additionalReqParams} */ - private static final Set KNOWN_REQ_PARAMS = new HashSet<>(); - static { - KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.CLIENT_ID_PARAM); - KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.RESPONSE_TYPE_PARAM); - KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.RESPONSE_MODE_PARAM); - KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.REDIRECT_URI_PARAM); - KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.STATE_PARAM); - KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.SCOPE_PARAM); - KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.LOGIN_HINT_PARAM); - KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.PROMPT_PARAM); - KNOWN_REQ_PARAMS.add(AdapterConstants.KC_IDP_HINT); - KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.NONCE_PARAM); - KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.MAX_AGE_PARAM); - KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.UI_LOCALES_PARAM); - KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.REQUEST_PARAM); - KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.REQUEST_URI_PARAM); - } private enum Action { REGISTER, CODE, FORGOT_CREDENTIALS @@ -116,19 +77,8 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase { private OIDCResponseType parsedResponseType; private OIDCResponseMode parsedResponseMode; - private String clientId; + private AuthorizationEndpointRequest request; private String redirectUri; - private String redirectUriParam; - private String responseType; - private String responseMode; - private String state; - private String scope; - private String loginHint; - private String prompt; - private String nonce; - private String maxAge; - private String idpHint; - protected Map additionalReqParams = new HashMap<>(); public AuthorizationEndpoint(RealmModel realm, EventBuilder event) { super(realm, event); @@ -139,34 +89,25 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase { public Response build() { MultivaluedMap params = uriInfo.getQueryParameters(); - clientId = params.getFirst(OIDCLoginProtocol.CLIENT_ID_PARAM); - responseType = params.getFirst(OIDCLoginProtocol.RESPONSE_TYPE_PARAM); - responseMode = params.getFirst(OIDCLoginProtocol.RESPONSE_MODE_PARAM); - redirectUriParam = params.getFirst(OIDCLoginProtocol.REDIRECT_URI_PARAM); - state = params.getFirst(OIDCLoginProtocol.STATE_PARAM); - scope = params.getFirst(OIDCLoginProtocol.SCOPE_PARAM); - loginHint = params.getFirst(OIDCLoginProtocol.LOGIN_HINT_PARAM); - prompt = params.getFirst(OIDCLoginProtocol.PROMPT_PARAM); - idpHint = params.getFirst(AdapterConstants.KC_IDP_HINT); - nonce = params.getFirst(OIDCLoginProtocol.NONCE_PARAM); - maxAge = params.getFirst(OIDCLoginProtocol.MAX_AGE_PARAM); - - extractAdditionalReqParams(params); + String clientId = params.getFirst(OIDCLoginProtocol.CLIENT_ID_PARAM); checkSsl(); checkRealm(); - checkClient(); + checkClient(clientId); + + request = AuthorizationEndpointRequestParserProcessor.parseRequest(event, session, client, params); + checkRedirectUri(); Response errorResponse = checkResponseType(); if (errorResponse != null) { return errorResponse; } - if (!TokenUtil.isOIDCRequest(scope)) { + if (!TokenUtil.isOIDCRequest(request.getScope())) { logger.oidcScopeMissing(); } - errorResponse = checkOIDCParams(params); + errorResponse = checkOIDCParams(); if (errorResponse != null) { return errorResponse; } @@ -186,27 +127,6 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase { throw new RuntimeException("Unknown action " + action); } - protected void extractAdditionalReqParams(MultivaluedMap params) { - for (String paramName : params.keySet()) { - if (!KNOWN_REQ_PARAMS.contains(paramName)) { - String value = params.getFirst(paramName); - if (value != null && value.trim().isEmpty()) { - value = null; - } - if (value != null && value.length() <= ADDITIONAL_REQ_PARAMS_MAX_SIZE) { - if (additionalReqParams.size() >= ADDITIONAL_REQ_PARAMS_MAX_MUMBER) { - logger.debug("Maximal number of additional OIDC params (" + ADDITIONAL_REQ_PARAMS_MAX_MUMBER + ") exceeded, ignoring rest of them!"); - break; - } - additionalReqParams.put(paramName, value); - } else { - logger.debug("OIDC Additional param " + paramName + " ignored because value is empty or longer than " + ADDITIONAL_REQ_PARAMS_MAX_SIZE); - } - } - - } - } - public AuthorizationEndpoint register() { event.event(EventType.REGISTER); action = Action.REGISTER; @@ -243,7 +163,7 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase { } } - private void checkClient() { + private void checkClient(String clientId) { if (clientId == null) { event.error(Errors.INVALID_REQUEST); throw new ErrorPageException(session, Messages.MISSING_PARAMETER, OIDCLoginProtocol.CLIENT_ID_PARAM); @@ -271,6 +191,8 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase { } private Response checkResponseType() { + String responseType = request.getResponseType(); + if (responseType == null) { logger.missingParameter(OAuth2Constants.RESPONSE_TYPE); event.error(Errors.INVALID_REQUEST); @@ -292,7 +214,7 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase { OIDCResponseMode parsedResponseMode = null; try { - parsedResponseMode = OIDCResponseMode.parse(responseMode, parsedResponseType); + parsedResponseMode = OIDCResponseMode.parse(request.getResponseMode(), parsedResponseType); } catch (IllegalArgumentException iae) { logger.invalidParameter(OIDCLoginProtocol.RESPONSE_MODE_PARAM); event.error(Errors.INVALID_REQUEST); @@ -325,20 +247,8 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase { return null; } - private Response checkOIDCParams(MultivaluedMap params) { - if (params.getFirst(OIDCLoginProtocol.REQUEST_PARAM) != null) { - logger.unsupportedParameter(OIDCLoginProtocol.REQUEST_PARAM); - event.error(Errors.INVALID_REQUEST); - return redirectErrorToClient(parsedResponseMode, OAuthErrorException.REQUEST_NOT_SUPPORTED, null); - } - - if (params.getFirst(OIDCLoginProtocol.REQUEST_URI_PARAM) != null) { - logger.unsupportedParameter(OIDCLoginProtocol.REQUEST_URI_PARAM); - event.error(Errors.INVALID_REQUEST); - return redirectErrorToClient(parsedResponseMode, OAuthErrorException.REQUEST_URI_NOT_SUPPORTED, null); - } - - if (parsedResponseType.isImplicitOrHybridFlow() && nonce == null) { + private Response checkOIDCParams() { + if (parsedResponseType.isImplicitOrHybridFlow() && request.getNonce() == null) { logger.missingParameter(OIDCLoginProtocol.NONCE_PARAM); event.error(Errors.INVALID_REQUEST); return redirectErrorToClient(parsedResponseMode, OAuthErrorException.INVALID_REQUEST, "Missing parameter: nonce"); @@ -355,14 +265,16 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase { errorResponseBuilder.addParam(OAuth2Constants.ERROR_DESCRIPTION, errorDescription); } - if (state != null) { - errorResponseBuilder.addParam(OAuth2Constants.STATE, state); + if (request.getState() != null) { + errorResponseBuilder.addParam(OAuth2Constants.STATE, request.getState()); } return errorResponseBuilder.build(); } private void checkRedirectUri() { + String redirectUriParam = request.getRedirectUriParam(); + event.detail(Details.REDIRECT_URI, redirectUriParam); redirectUri = RedirectUtils.verifyRedirectUri(uriInfo, redirectUriParam, realm, client); @@ -377,43 +289,31 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase { clientSession.setAuthMethod(OIDCLoginProtocol.LOGIN_PROTOCOL); clientSession.setRedirectUri(redirectUri); clientSession.setAction(ClientSessionModel.Action.AUTHENTICATE.name()); - clientSession.setNote(OIDCLoginProtocol.RESPONSE_TYPE_PARAM, responseType); - clientSession.setNote(OIDCLoginProtocol.REDIRECT_URI_PARAM, redirectUriParam); + clientSession.setNote(OIDCLoginProtocol.RESPONSE_TYPE_PARAM, request.getResponseType()); + clientSession.setNote(OIDCLoginProtocol.REDIRECT_URI_PARAM, request.getRedirectUriParam()); clientSession.setNote(OIDCLoginProtocol.ISSUER, Urls.realmIssuer(uriInfo.getBaseUri(), realm.getName())); - if (state != null) clientSession.setNote(OIDCLoginProtocol.STATE_PARAM, state); - if (nonce != null) clientSession.setNote(OIDCLoginProtocol.NONCE_PARAM, nonce); - if (maxAge != null) clientSession.setNote(OIDCLoginProtocol.MAX_AGE_PARAM, maxAge); - if (scope != null) clientSession.setNote(OIDCLoginProtocol.SCOPE_PARAM, scope); - if (loginHint != null) clientSession.setNote(OIDCLoginProtocol.LOGIN_HINT_PARAM, loginHint); - if (prompt != null) clientSession.setNote(OIDCLoginProtocol.PROMPT_PARAM, prompt); - if (idpHint != null) clientSession.setNote(AdapterConstants.KC_IDP_HINT, idpHint); - if (responseMode != null) clientSession.setNote(OIDCLoginProtocol.RESPONSE_MODE_PARAM, responseMode); + if (request.getState() != null) clientSession.setNote(OIDCLoginProtocol.STATE_PARAM, request.getState()); + if (request.getNonce() != null) clientSession.setNote(OIDCLoginProtocol.NONCE_PARAM, request.getNonce()); + if (request.getMaxAge() != null) clientSession.setNote(OIDCLoginProtocol.MAX_AGE_PARAM, String.valueOf(request.getMaxAge())); + if (request.getScope() != null) clientSession.setNote(OIDCLoginProtocol.SCOPE_PARAM, request.getScope()); + if (request.getLoginHint() != null) clientSession.setNote(OIDCLoginProtocol.LOGIN_HINT_PARAM, request.getLoginHint()); + if (request.getPrompt() != null) clientSession.setNote(OIDCLoginProtocol.PROMPT_PARAM, request.getPrompt()); + if (request.getIdpHint() != null) clientSession.setNote(AdapterConstants.KC_IDP_HINT, request.getIdpHint()); + if (request.getResponseMode() != null) clientSession.setNote(OIDCLoginProtocol.RESPONSE_MODE_PARAM, request.getResponseMode()); - if (additionalReqParams != null) { - for (String paramName : additionalReqParams.keySet()) { - clientSession.setNote(CLIENT_SESSION_NOTE_ADDITIONAL_REQ_PARAMS_PREFIX + paramName, additionalReqParams.get(paramName)); + if (request.getAdditionalReqParams() != null) { + for (String paramName : request.getAdditionalReqParams().keySet()) { + clientSession.setNote(CLIENT_SESSION_NOTE_ADDITIONAL_REQ_PARAMS_PREFIX + paramName, request.getAdditionalReqParams().get(paramName)); } } } private Response buildAuthorizationCodeAuthorizationResponse() { - - if (idpHint != null && !"".equals(idpHint)) { - IdentityProviderModel identityProviderModel = realm.getIdentityProviderByAlias(idpHint); - - if (identityProviderModel == null) { - return session.getProvider(LoginFormsProvider.class) - .setError(Messages.IDENTITY_PROVIDER_NOT_FOUND, idpHint) - .createErrorPage(); - } - return buildRedirectToIdentityProvider(idpHint, new ClientSessionCode(realm, clientSession).getCode()); - } - this.event.event(EventType.LOGIN); clientSession.setNote(Details.AUTH_TYPE, CODE_AUTH_TYPE); - return handleBrowserAuthenticationRequest(clientSession, new OIDCLoginProtocol(session, realm, uriInfo, headers, event), TokenUtil.hasPrompt(prompt, OIDCLoginProtocol.PROMPT_VALUE_NONE), false); + return handleBrowserAuthenticationRequest(clientSession, new OIDCLoginProtocol(session, realm, uriInfo, headers, event), TokenUtil.hasPrompt(request.getPrompt(), OIDCLoginProtocol.PROMPT_VALUE_NONE), false); } private Response buildRegister() { diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/UserInfoEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/UserInfoEndpoint.java index f5e4caab17..28d951438a 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/UserInfoEndpoint.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/UserInfoEndpoint.java @@ -19,10 +19,9 @@ package org.keycloak.protocol.oidc.endpoints; import org.jboss.resteasy.annotations.cache.NoCache; import org.jboss.resteasy.spi.HttpRequest; import org.jboss.resteasy.spi.HttpResponse; -import org.keycloak.OAuth2Constants; -import org.keycloak.common.ClientConnection; import org.keycloak.OAuthErrorException; import org.keycloak.RSATokenVerifier; +import org.keycloak.common.ClientConnection; import org.keycloak.common.VerificationException; import org.keycloak.events.Details; import org.keycloak.events.Errors; @@ -30,20 +29,15 @@ import org.keycloak.events.EventBuilder; import org.keycloak.events.EventType; import org.keycloak.jose.jws.Algorithm; import org.keycloak.jose.jws.JWSBuilder; -import org.keycloak.models.ClientModel; -import org.keycloak.models.ClientSessionModel; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.RealmModel; -import org.keycloak.models.UserModel; -import org.keycloak.models.UserSessionModel; +import org.keycloak.models.*; import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper; import org.keycloak.protocol.oidc.TokenManager; import org.keycloak.representations.AccessToken; import org.keycloak.services.ErrorResponseException; +import org.keycloak.services.Urls; import org.keycloak.services.managers.AppAuthManager; import org.keycloak.services.managers.AuthenticationManager; import org.keycloak.services.resources.Cors; -import org.keycloak.services.Urls; import org.keycloak.utils.MediaType; import javax.ws.rs.GET; @@ -54,7 +48,6 @@ import javax.ws.rs.core.Context; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.Response; import javax.ws.rs.core.UriInfo; - import java.security.PrivateKey; import java.util.HashMap; import java.util.Map; @@ -179,8 +172,8 @@ public class UserInfoEndpoint { tokenManager.transformUserInfoAccessToken(session, userInfo, realm, clientModel, userModel, userSession, clientSession); Map claims = new HashMap(); - claims.putAll(userInfo.getOtherClaims()); claims.put("sub", userModel.getId()); + claims.putAll(userInfo.getOtherClaims()); Response.ResponseBuilder responseBuilder; OIDCAdvancedConfigWrapper cfg = OIDCAdvancedConfigWrapper.fromClientModel(clientModel); diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/request/AuthorizationEndpointRequest.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/request/AuthorizationEndpointRequest.java new file mode 100644 index 0000000000..998a58ca45 --- /dev/null +++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/request/AuthorizationEndpointRequest.java @@ -0,0 +1,88 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.keycloak.protocol.oidc.endpoints.request; + +import java.util.HashMap; +import java.util.Map; + +/** + * @author Marek Posolda + */ +public class AuthorizationEndpointRequest { + + String clientId; + String redirectUriParam; + String responseType; + String responseMode; + String state; + String scope; + String loginHint; + String prompt; + String nonce; + Integer maxAge; + String idpHint; + Map additionalReqParams = new HashMap<>(); + + public String getClientId() { + return clientId; + } + + public String getRedirectUriParam() { + return redirectUriParam; + } + + public String getResponseType() { + return responseType; + } + + public String getResponseMode() { + return responseMode; + } + + public String getState() { + return state; + } + + public String getScope() { + return scope; + } + + public String getLoginHint() { + return loginHint; + } + + public String getPrompt() { + return prompt; + } + + public String getNonce() { + return nonce; + } + + public Integer getMaxAge() { + return maxAge; + } + + public String getIdpHint() { + return idpHint; + } + + public Map getAdditionalReqParams() { + return additionalReqParams; + } +} diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/request/AuthorizationEndpointRequestParserProcessor.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/request/AuthorizationEndpointRequestParserProcessor.java new file mode 100644 index 0000000000..8db219c08f --- /dev/null +++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/request/AuthorizationEndpointRequestParserProcessor.java @@ -0,0 +1,73 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.keycloak.protocol.oidc.endpoints.request; + +import java.io.InputStream; +import java.util.Map; + +import javax.ws.rs.core.MultivaluedMap; + +import org.keycloak.common.util.StreamUtil; +import org.keycloak.connections.httpclient.HttpClientProvider; +import org.keycloak.events.Errors; +import org.keycloak.events.EventBuilder; +import org.keycloak.models.ClientModel; +import org.keycloak.models.KeycloakSession; +import org.keycloak.protocol.oidc.OIDCLoginProtocol; +import org.keycloak.services.ErrorPageException; +import org.keycloak.services.ServicesLogger; +import org.keycloak.services.messages.Messages; + +/** + * @author Marek Posolda + */ +public class AuthorizationEndpointRequestParserProcessor { + + private static final ServicesLogger logger = ServicesLogger.ROOT_LOGGER; + + public static AuthorizationEndpointRequest parseRequest(EventBuilder event, KeycloakSession session, ClientModel client, MultivaluedMap requestParams) { + try { + AuthorizationEndpointRequest request = new AuthorizationEndpointRequest(); + + new AuthzEndpointQueryStringParser(requestParams).parseRequest(request); + + String requestParam = requestParams.getFirst(OIDCLoginProtocol.REQUEST_PARAM); + String requestUriParam = requestParams.getFirst(OIDCLoginProtocol.REQUEST_URI_PARAM); + + if (requestParam != null && requestUriParam != null) { + throw new RuntimeException("Illegal to use both 'request' and 'request_uri' parameters together"); + } + + if (requestParam != null) { + new AuthzEndpointRequestObjectParser(requestParam, client).parseRequest(request); + } else if (requestUriParam != null) { + InputStream is = session.getProvider(HttpClientProvider.class).get(requestUriParam); + String retrievedRequest = StreamUtil.readString(is); + + new AuthzEndpointRequestObjectParser(retrievedRequest, client).parseRequest(request); + } + + return request; + + } catch (Exception e) { + logger.invalidRequest(e); + event.error(Errors.INVALID_REQUEST); + throw new ErrorPageException(session, Messages.INVALID_REQUEST); + } + } +} diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/request/AuthzEndpointQueryStringParser.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/request/AuthzEndpointQueryStringParser.java new file mode 100644 index 0000000000..8384fdcdcb --- /dev/null +++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/request/AuthzEndpointQueryStringParser.java @@ -0,0 +1,52 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.keycloak.protocol.oidc.endpoints.request; + +import java.util.Set; + +import javax.ws.rs.core.MultivaluedMap; + +/** + * Parse the parameters from request queryString + * + * @author Marek Posolda + */ +class AuthzEndpointQueryStringParser extends AuthzEndpointRequestParser { + + private final MultivaluedMap requestParams; + + public AuthzEndpointQueryStringParser(MultivaluedMap requestParams) { + this.requestParams = requestParams; + } + + @Override + protected String getParameter(String paramName) { + return requestParams.getFirst(paramName); + } + + @Override + protected Integer getIntParameter(String paramName) { + String paramVal = requestParams.getFirst(paramName); + return paramVal==null ? null : Integer.parseInt(paramVal); + } + + @Override + protected Set keySet() { + return requestParams.keySet(); + } +} diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/request/AuthzEndpointRequestObjectParser.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/request/AuthzEndpointRequestObjectParser.java new file mode 100644 index 0000000000..658a2c002e --- /dev/null +++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/request/AuthzEndpointRequestObjectParser.java @@ -0,0 +1,88 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.keycloak.protocol.oidc.endpoints.request; + +import java.security.PublicKey; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import org.keycloak.authentication.authenticators.client.JWTClientAuthenticator; +import org.keycloak.jose.jws.Algorithm; +import org.keycloak.jose.jws.JWSHeader; +import org.keycloak.jose.jws.JWSInput; +import org.keycloak.jose.jws.crypto.RSAProvider; +import org.keycloak.models.ClientModel; +import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper; +import org.keycloak.services.util.CertificateInfoHelper; +import org.keycloak.util.JsonSerialization; + +/** + * Parse the parameters from OIDC "request" object + * + * @author Marek Posolda + */ +class AuthzEndpointRequestObjectParser extends AuthzEndpointRequestParser { + + private final Map requestParams; + + public AuthzEndpointRequestObjectParser(String requestObject, ClientModel client) throws Exception { + JWSInput input = new JWSInput(requestObject); + JWSHeader header = input.getHeader(); + + Algorithm requestedSignatureAlgorithm = OIDCAdvancedConfigWrapper.fromClientModel(client).getRequestObjectSignatureAlg(); + + if (requestedSignatureAlgorithm != null && requestedSignatureAlgorithm != header.getAlgorithm()) { + throw new RuntimeException("Request object signed with different algorithm than client requested algorithm"); + } + + if (header.getAlgorithm() == Algorithm.none) { + this.requestParams = JsonSerialization.readValue(input.getContent(), TypedHashMap.class); + } else if (header.getAlgorithm() == Algorithm.RS256) { + PublicKey clientPublicKey = CertificateInfoHelper.getSignatureValidationKey(client, JWTClientAuthenticator.ATTR_PREFIX); + boolean verified = RSAProvider.verify(input, clientPublicKey); + if (!verified) { + throw new RuntimeException("Failed to verify signature on 'request' object"); + } + + this.requestParams = JsonSerialization.readValue(input.getContent(), TypedHashMap.class); + } else { + throw new RuntimeException("Unsupported JWA algorithm used for signed request"); + } + } + + @Override + protected String getParameter(String paramName) { + Object val = this.requestParams.get(paramName); + return val==null ? null : val.toString(); + } + + @Override + protected Integer getIntParameter(String paramName) { + Object val = this.requestParams.get(paramName); + return val==null ? null : Integer.parseInt(getParameter(paramName)); + } + + @Override + protected Set keySet() { + return requestParams.keySet(); + } + + static class TypedHashMap extends HashMap { + } +} diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/request/AuthzEndpointRequestParser.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/request/AuthzEndpointRequestParser.java new file mode 100644 index 0000000000..e322d4b75e --- /dev/null +++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/request/AuthzEndpointRequestParser.java @@ -0,0 +1,122 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.keycloak.protocol.oidc.endpoints.request; + +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.keycloak.constants.AdapterConstants; +import org.keycloak.protocol.oidc.OIDCLoginProtocol; +import org.keycloak.services.ServicesLogger; + +/** + * @author Marek Posolda + */ +abstract class AuthzEndpointRequestParser { + + private static final ServicesLogger logger = ServicesLogger.ROOT_LOGGER; + + /** + * Max number of additional req params copied into client session note to prevent DoS attacks + * + */ + public static final int ADDITIONAL_REQ_PARAMS_MAX_MUMBER = 5; + + /** + * Max size of additional req param value copied into client session note to prevent DoS attacks - params with longer value are ignored + * + */ + public static final int ADDITIONAL_REQ_PARAMS_MAX_SIZE = 200; + + /** Set of known protocol GET params not to be stored into additionalReqParams} */ + private static final Set KNOWN_REQ_PARAMS = new HashSet<>(); + static { + KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.CLIENT_ID_PARAM); + KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.RESPONSE_TYPE_PARAM); + KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.RESPONSE_MODE_PARAM); + KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.REDIRECT_URI_PARAM); + KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.STATE_PARAM); + KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.SCOPE_PARAM); + KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.LOGIN_HINT_PARAM); + KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.PROMPT_PARAM); + KNOWN_REQ_PARAMS.add(AdapterConstants.KC_IDP_HINT); + KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.NONCE_PARAM); + KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.MAX_AGE_PARAM); + KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.UI_LOCALES_PARAM); + KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.REQUEST_PARAM); + KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.REQUEST_URI_PARAM); + } + + + public void parseRequest(AuthorizationEndpointRequest request) { + String clientId = getParameter(OIDCLoginProtocol.CLIENT_ID_PARAM); + + if (request.clientId != null && !request.clientId.equals(clientId)) { + throw new IllegalArgumentException("The client_id parameter doesn't match the one from OIDC 'request' or 'request_uri'"); + } + + request.clientId = clientId; + request.responseType = replaceIfNotNull(request.responseType, getParameter(OIDCLoginProtocol.RESPONSE_TYPE_PARAM)); + request.responseMode = replaceIfNotNull(request.responseMode, getParameter(OIDCLoginProtocol.RESPONSE_MODE_PARAM)); + request.redirectUriParam = replaceIfNotNull(request.redirectUriParam, getParameter(OIDCLoginProtocol.REDIRECT_URI_PARAM)); + request.state = replaceIfNotNull(request.state, getParameter(OIDCLoginProtocol.STATE_PARAM)); + request.scope = replaceIfNotNull(request.scope, getParameter(OIDCLoginProtocol.SCOPE_PARAM)); + request.loginHint = replaceIfNotNull(request.loginHint, getParameter(OIDCLoginProtocol.LOGIN_HINT_PARAM)); + request.prompt = replaceIfNotNull(request.prompt, getParameter(OIDCLoginProtocol.PROMPT_PARAM)); + request.idpHint = replaceIfNotNull(request.idpHint, getParameter(AdapterConstants.KC_IDP_HINT)); + request.nonce = replaceIfNotNull(request.nonce, getParameter(OIDCLoginProtocol.NONCE_PARAM)); + request.maxAge = replaceIfNotNull(request.maxAge, getIntParameter(OIDCLoginProtocol.MAX_AGE_PARAM)); + + extractAdditionalReqParams(request.additionalReqParams); + } + + + protected void extractAdditionalReqParams(Map additionalReqParams) { + for (String paramName : keySet()) { + if (!KNOWN_REQ_PARAMS.contains(paramName)) { + String value = getParameter(paramName); + if (value != null && value.trim().isEmpty()) { + value = null; + } + if (value != null && value.length() <= ADDITIONAL_REQ_PARAMS_MAX_SIZE) { + if (additionalReqParams.size() >= ADDITIONAL_REQ_PARAMS_MAX_MUMBER) { + logger.debug("Maximal number of additional OIDC params (" + ADDITIONAL_REQ_PARAMS_MAX_MUMBER + ") exceeded, ignoring rest of them!"); + break; + } + additionalReqParams.put(paramName, value); + } else { + logger.debug("OIDC Additional param " + paramName + " ignored because value is empty or longer than " + ADDITIONAL_REQ_PARAMS_MAX_SIZE); + } + } + + } + } + + protected T replaceIfNotNull(T previousVal, T newVal) { + return newVal==null ? previousVal : newVal; + } + + + protected abstract String getParameter(String paramName); + + protected abstract Integer getIntParameter(String paramName); + + protected abstract Set keySet(); + +} diff --git a/services/src/main/java/org/keycloak/protocol/oidc/installation/KeycloakOIDCClientInstallation.java b/services/src/main/java/org/keycloak/protocol/oidc/installation/KeycloakOIDCClientInstallation.java index 6fda3f0394..79a3919be5 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/installation/KeycloakOIDCClientInstallation.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/installation/KeycloakOIDCClientInstallation.java @@ -51,7 +51,6 @@ public class KeycloakOIDCClientInstallation implements ClientInstallationProvide ClientManager.InstallationAdapterConfig rep = new ClientManager.InstallationAdapterConfig(); rep.setAuthServerUrl(baseUri.toString()); rep.setRealm(realm.getName()); - rep.setRealmKey(realm.getPublicKeyPem()); rep.setSslRequired(realm.getSslRequired().name().toLowerCase()); if (client.isPublicClient() && !client.isBearerOnly()) rep.setPublicClient(true); diff --git a/services/src/main/java/org/keycloak/protocol/oidc/installation/KeycloakOIDCJbossSubsystemClientInstallation.java b/services/src/main/java/org/keycloak/protocol/oidc/installation/KeycloakOIDCJbossSubsystemClientInstallation.java index 33ab67787d..d0bc939eb2 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/installation/KeycloakOIDCJbossSubsystemClientInstallation.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/installation/KeycloakOIDCJbossSubsystemClientInstallation.java @@ -40,7 +40,6 @@ public class KeycloakOIDCJbossSubsystemClientInstallation implements ClientInsta StringBuffer buffer = new StringBuffer(); buffer.append("\n"); buffer.append(" ").append(realm.getName()).append("\n"); - buffer.append(" ").append(realm.getPublicKeyPem()).append("\n"); buffer.append(" ").append(baseUri.toString()).append("\n"); if (client.isBearerOnly()){ buffer.append(" true\n"); diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/AbstractPairwiseSubMapper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/AbstractPairwiseSubMapper.java new file mode 100644 index 0000000000..b153fe48a1 --- /dev/null +++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/AbstractPairwiseSubMapper.java @@ -0,0 +1,125 @@ +package org.keycloak.protocol.oidc.mappers; + +import org.keycloak.models.*; +import org.keycloak.protocol.ProtocolMapperConfigException; +import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper; +import org.keycloak.protocol.oidc.utils.PairwiseSubMapperValidator; +import org.keycloak.protocol.oidc.utils.PairwiseSubMapperUtils; +import org.keycloak.provider.ProviderConfigProperty; +import org.keycloak.representations.AccessToken; +import org.keycloak.representations.IDToken; + +import java.util.LinkedList; +import java.util.List; + +/** + * Set the 'sub' claim to pairwise . + * + * @author Martin Hardselius + */ +public abstract class AbstractPairwiseSubMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper, OIDCIDTokenMapper, UserInfoTokenMapper { + public static final String PROVIDER_ID_SUFFIX = "-pairwise-sub-mapper"; + + public static String getId(String prefix) { + return prefix + PROVIDER_ID_SUFFIX; + } + + public abstract String getIdPrefix(); + + /** + * Generates a pairwise subject identifier. + * + * @param mappingModel + * @param sectorIdentifier client sector identifier + * @param localSub local subject identifier (user id) + * @return A pairwise subject identifier + */ + public abstract String generateSub(ProtocolMapperModel mappingModel, String sectorIdentifier, String localSub); + + /** + * Override to add additional provider configuration properties. By default, a pairwise sub mapper will only contain configuration for a sector identifier URI. + * + * @return A list of provider configuration properties. + */ + public List getAdditionalConfigProperties() { + return new LinkedList<>(); + } + + /** + * Override to add additional configuration validation. Called when instance of mapperModel is created/updated for this protocolMapper through admin endpoint. + * + * @param session + * @param realm + * @param mapperContainer client or clientTemplate + * @param mapperModel + * @throws ProtocolMapperConfigException if configuration provided in mapperModel is not valid + */ + public void validateAdditionalConfig(KeycloakSession session, RealmModel realm, ProtocolMapperContainerModel mapperContainer, ProtocolMapperModel mapperModel) throws ProtocolMapperConfigException { + } + + @Override + public final String getDisplayCategory() { + return AbstractOIDCProtocolMapper.TOKEN_MAPPER_CATEGORY; + } + + @Override + public final IDToken transformIDToken(IDToken token, ProtocolMapperModel mappingModel, KeycloakSession session, UserSessionModel userSession, ClientSessionModel clientSession) { + setSubject(token, generateSub(mappingModel, getSectorIdentifier(clientSession.getClient(), mappingModel), userSession.getUser().getId())); + return token; + } + + @Override + public final AccessToken transformAccessToken(AccessToken token, ProtocolMapperModel mappingModel, KeycloakSession session, UserSessionModel userSession, ClientSessionModel clientSession) { + setSubject(token, generateSub(mappingModel, getSectorIdentifier(clientSession.getClient(), mappingModel), userSession.getUser().getId())); + return token; + } + + @Override + public final AccessToken transformUserInfoToken(AccessToken token, ProtocolMapperModel mappingModel, KeycloakSession session, UserSessionModel userSession, ClientSessionModel clientSession) { + setSubject(token, generateSub(mappingModel, getSectorIdentifier(clientSession.getClient(), mappingModel), userSession.getUser().getId())); + return token; + } + + private void setSubject(IDToken token, String pairwiseSub) { + token.getOtherClaims().put("sub", pairwiseSub); + } + + @Override + public final List getConfigProperties() { + List configProperties = new LinkedList<>(); + configProperties.add(PairwiseSubMapperHelper.createSectorIdentifierConfig()); + configProperties.addAll(getAdditionalConfigProperties()); + return configProperties; + } + + private String getSectorIdentifier(ClientModel client, ProtocolMapperModel mappingModel) { + String sectorIdentifierUri = PairwiseSubMapperHelper.getSectorIdentifierUri(mappingModel); + if (sectorIdentifierUri != null && !sectorIdentifierUri.isEmpty()) { + return PairwiseSubMapperUtils.resolveValidSectorIdentifier(sectorIdentifierUri); + } + return PairwiseSubMapperUtils.resolveValidSectorIdentifier(client.getRootUrl(), client.getRedirectUris()); + } + + @Override + public final void validateConfig(KeycloakSession session, RealmModel realm, ProtocolMapperContainerModel mapperContainer, ProtocolMapperModel mapperModel) throws ProtocolMapperConfigException { + ClientModel client = null; + if (mapperContainer instanceof ClientModel) { + client = (ClientModel) mapperContainer; + PairwiseSubMapperValidator.validate(session, client, mapperModel); + } + validateAdditionalConfig(session, realm, mapperContainer, mapperModel); + + if (client != null) { + // Propagate changes to the sector identifier uri + OIDCAdvancedConfigWrapper configWrapper = OIDCAdvancedConfigWrapper.fromClientModel(client); + configWrapper.setSectorIdentifierUri(PairwiseSubMapperHelper.getSectorIdentifierUri(mapperModel)); + } + } + + @Override + public final String getId() { + return getIdPrefix() + PROVIDER_ID_SUFFIX; + } +} + + diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/PairwiseSubMapperHelper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/PairwiseSubMapperHelper.java new file mode 100644 index 0000000000..a2aa4b3970 --- /dev/null +++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/PairwiseSubMapperHelper.java @@ -0,0 +1,47 @@ +package org.keycloak.protocol.oidc.mappers; + +import org.keycloak.models.ProtocolMapperModel; +import org.keycloak.provider.ProviderConfigProperty; +import org.keycloak.services.ServicesLogger; + +public class PairwiseSubMapperHelper { + private static final ServicesLogger logger = ServicesLogger.ROOT_LOGGER; + + public static final String SECTOR_IDENTIFIER_URI = "sectorIdentifierUri"; + public static final String SECTOR_IDENTIFIER_URI_LABEL = "sectorIdentifierUri.label"; + public static final String SECTOR_IDENTIFIER_URI_HELP_TEXT = "sectorIdentifierUri.tooltip"; + + public static final String PAIRWISE_SUB_ALGORITHM_SALT = "pairwiseSubAlgorithmSalt"; + public static final String PAIRWISE_SUB_ALGORITHM_SALT_LABEL = "pairwiseSubAlgorithmSalt.label"; + public static final String PAIRWISE_SUB_ALGORITHM_SALT_HELP_TEXT = "pairwiseSubAlgorithmSalt.tooltip"; + + public static String getSectorIdentifierUri(ProtocolMapperModel mappingModel) { + return mappingModel.getConfig().get(SECTOR_IDENTIFIER_URI); + } + + public static String getSalt(ProtocolMapperModel mappingModel) { + return mappingModel.getConfig().get(PAIRWISE_SUB_ALGORITHM_SALT); + } + + public static void setSalt(ProtocolMapperModel mappingModel, String salt) { + mappingModel.getConfig().put(PAIRWISE_SUB_ALGORITHM_SALT, salt); + } + + public static ProviderConfigProperty createSectorIdentifierConfig() { + ProviderConfigProperty property = new ProviderConfigProperty(); + property.setName(SECTOR_IDENTIFIER_URI); + property.setType(ProviderConfigProperty.STRING_TYPE); + property.setLabel(SECTOR_IDENTIFIER_URI_LABEL); + property.setHelpText(SECTOR_IDENTIFIER_URI_HELP_TEXT); + return property; + } + + public static ProviderConfigProperty createSaltConfig() { + ProviderConfigProperty property = new ProviderConfigProperty(); + property.setName(PAIRWISE_SUB_ALGORITHM_SALT); + property.setType(ProviderConfigProperty.STRING_TYPE); + property.setLabel(PAIRWISE_SUB_ALGORITHM_SALT_LABEL); + property.setHelpText(PAIRWISE_SUB_ALGORITHM_SALT_HELP_TEXT); + return property; + } +} diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/SHA265PairwiseSubMapper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/SHA265PairwiseSubMapper.java new file mode 100644 index 0000000000..bc1caaad85 --- /dev/null +++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/SHA265PairwiseSubMapper.java @@ -0,0 +1,119 @@ +package org.keycloak.protocol.oidc.mappers; + +import org.keycloak.models.ProtocolMapperModel; +import org.keycloak.protocol.oidc.OIDCLoginProtocol; +import org.keycloak.provider.ProviderConfigProperty; +import org.keycloak.services.ServicesLogger; + +import java.nio.charset.Charset; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.util.*; + +public class SHA265PairwiseSubMapper extends AbstractPairwiseSubMapper { + public static final String PROVIDER_ID = "sha256"; + private static final String HASH_ALGORITHM = "SHA-256"; + private static final String ALPHA_NUMERIC = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + private static final ServicesLogger logger = ServicesLogger.ROOT_LOGGER; + private final Charset charset; + + public SHA265PairwiseSubMapper() throws NoSuchAlgorithmException { + charset = Charset.forName("UTF-8"); + MessageDigest.getInstance(HASH_ALGORITHM); + } + + public static ProtocolMapperModel createPairwiseMapper() { + return createPairwiseMapper(null); + } + + public static ProtocolMapperModel createPairwiseMapper(String sectorIdentifierUri) { + Map config; + ProtocolMapperModel pairwise = new ProtocolMapperModel(); + pairwise.setName("pairwise subject identifier"); + pairwise.setProtocolMapper(AbstractPairwiseSubMapper.getId(PROVIDER_ID)); + pairwise.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL); + pairwise.setConsentRequired(false); + config = new HashMap<>(); + config.put(PairwiseSubMapperHelper.SECTOR_IDENTIFIER_URI, sectorIdentifierUri); + pairwise.setConfig(config); + return pairwise; + } + + public static ProtocolMapperModel createPairwiseMapper(String sectorIdentifierUri, String salt) { + Map config; + ProtocolMapperModel pairwise = new ProtocolMapperModel(); + pairwise.setName("pairwise subject identifier"); + pairwise.setProtocolMapper(AbstractPairwiseSubMapper.getId(PROVIDER_ID)); + pairwise.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL); + pairwise.setConsentRequired(false); + config = new HashMap<>(); + config.put(PairwiseSubMapperHelper.SECTOR_IDENTIFIER_URI, sectorIdentifierUri); + config.put(PairwiseSubMapperHelper.PAIRWISE_SUB_ALGORITHM_SALT, salt); + pairwise.setConfig(config); + return pairwise; + } + + @Override + public String getHelpText() { + return "Calculates a pairwise subject identifier using a salted sha-256 hash."; + } + + @Override + public List getAdditionalConfigProperties() { + List configProperties = new LinkedList<>(); + configProperties.add(PairwiseSubMapperHelper.createSaltConfig()); + return configProperties; + } + + @Override + public String generateSub(ProtocolMapperModel mappingModel, String sectorIdentifier, String localSub) { + String saltStr = getSalt(mappingModel); + + Charset charset = Charset.forName("UTF-8"); + byte[] salt = saltStr.getBytes(charset); + String pairwiseSub = generateSub(sectorIdentifier, localSub, salt); + logger.infof("local sub = '%s', pairwise sub = '%s'", localSub, pairwiseSub); + return pairwiseSub; + } + + private String generateSub(String sectorIdentifier, String localSub, byte[] salt) { + MessageDigest sha256; + try { + sha256 = MessageDigest.getInstance(HASH_ALGORITHM); + } catch (NoSuchAlgorithmException e) { + throw new IllegalStateException(e.getMessage(), e); + } + sha256.update(sectorIdentifier.getBytes(charset)); + sha256.update(localSub.getBytes(charset)); + byte[] hash = sha256.digest(salt); + return UUID.nameUUIDFromBytes(hash).toString(); + } + + private String getSalt(ProtocolMapperModel mappingModel) { + String salt = PairwiseSubMapperHelper.getSalt(mappingModel); + if (salt == null || salt.trim().isEmpty()) { + salt = createSalt(32); + PairwiseSubMapperHelper.setSalt(mappingModel, salt); + } + return salt; + } + + private String createSalt(int len) { + Random rnd = new SecureRandom(); + StringBuilder sb = new StringBuilder(len); + for (int i = 0; i < len; i++) + sb.append(ALPHA_NUMERIC.charAt(rnd.nextInt(ALPHA_NUMERIC.length()))); + return sb.toString(); + } + + @Override + public String getDisplayType() { + return "Pairwise subject identifier"; + } + + @Override + public String getIdPrefix() { + return PROVIDER_ID; + } +} diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/UserAttributeMapper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/UserAttributeMapper.java index 56e7a48d2e..f7475a95cf 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/mappers/UserAttributeMapper.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/UserAttributeMapper.java @@ -19,10 +19,12 @@ package org.keycloak.protocol.oidc.mappers; import org.keycloak.models.ClientSessionModel; import org.keycloak.models.KeycloakSession; +import org.keycloak.models.ProtocolMapperContainerModel; import org.keycloak.models.ProtocolMapperModel; import org.keycloak.models.UserModel; import org.keycloak.models.UserSessionModel; import org.keycloak.models.utils.KeycloakModelUtils; +import org.keycloak.protocol.ProtocolMapperConfigException; import org.keycloak.protocol.ProtocolMapperUtils; import org.keycloak.provider.ProviderConfigProperty; import org.keycloak.representations.AccessToken; diff --git a/services/src/main/java/org/keycloak/protocol/oidc/representations/OIDCConfigurationRepresentation.java b/services/src/main/java/org/keycloak/protocol/oidc/representations/OIDCConfigurationRepresentation.java index ee5241baa0..181e0d28df 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/representations/OIDCConfigurationRepresentation.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/representations/OIDCConfigurationRepresentation.java @@ -67,6 +67,9 @@ public class OIDCConfigurationRepresentation { @JsonProperty("userinfo_signing_alg_values_supported") private List userInfoSigningAlgValuesSupported; + @JsonProperty("request_object_signing_alg_values_supported") + private List requestObjectSigningAlgValuesSupported; + @JsonProperty("response_modes_supported") private List responseModesSupported; @@ -195,6 +198,14 @@ public class OIDCConfigurationRepresentation { this.userInfoSigningAlgValuesSupported = userInfoSigningAlgValuesSupported; } + public List getRequestObjectSigningAlgValuesSupported() { + return requestObjectSigningAlgValuesSupported; + } + + public void setRequestObjectSigningAlgValuesSupported(List requestObjectSigningAlgValuesSupported) { + this.requestObjectSigningAlgValuesSupported = requestObjectSigningAlgValuesSupported; + } + public List getResponseModesSupported() { return responseModesSupported; } diff --git a/services/src/main/java/org/keycloak/protocol/oidc/utils/JWKSUtils.java b/services/src/main/java/org/keycloak/protocol/oidc/utils/JWKSUtils.java index c856a81cef..62d2c58039 100644 --- a/services/src/main/java/org/keycloak/protocol/oidc/utils/JWKSUtils.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/utils/JWKSUtils.java @@ -18,21 +18,27 @@ package org.keycloak.protocol.oidc.utils; import java.io.IOException; +import java.io.InputStream; import java.security.PublicKey; -import org.keycloak.broker.provider.util.SimpleHttp; +import org.keycloak.common.util.StreamUtil; +import org.keycloak.connections.httpclient.HttpClientProvider; import org.keycloak.jose.jwk.JSONWebKeySet; import org.keycloak.jose.jwk.JWK; import org.keycloak.jose.jwk.JWKParser; +import org.keycloak.models.KeycloakSession; import org.keycloak.util.JsonSerialization; /** + * TODO: Merge with JWKSUtils from keycloak-core? + * * @author Marek Posolda */ public class JWKSUtils { - public static JSONWebKeySet sendJwksRequest(String jwksURI) throws IOException { - String keySetString = SimpleHttp.doGet(jwksURI).asString(); + public static JSONWebKeySet sendJwksRequest(KeycloakSession session, String jwksURI) throws IOException { + InputStream is = session.getProvider(HttpClientProvider.class).get(jwksURI); + String keySetString = StreamUtil.readString(is); return JsonSerialization.readValue(keySetString, JSONWebKeySet.class); } @@ -40,7 +46,7 @@ public class JWKSUtils { public static PublicKey getKeyForUse(JSONWebKeySet keySet, JWK.Use requestedUse) { for (JWK jwk : keySet.getKeys()) { JWKParser parser = JWKParser.create(jwk); - if (parser.getJwk().getPublicKeyUse().equals(requestedUse.asString()) && parser.isAlgorithmSupported(jwk.getKeyType())) { + if (parser.getJwk().getPublicKeyUse().equals(requestedUse.asString()) && parser.isKeyTypeSupported(jwk.getKeyType())) { return parser.toPublicKey(); } } diff --git a/services/src/main/java/org/keycloak/protocol/oidc/utils/PairwiseSubMapperUtils.java b/services/src/main/java/org/keycloak/protocol/oidc/utils/PairwiseSubMapperUtils.java new file mode 100644 index 0000000000..5e2dadcdc8 --- /dev/null +++ b/services/src/main/java/org/keycloak/protocol/oidc/utils/PairwiseSubMapperUtils.java @@ -0,0 +1,159 @@ +package org.keycloak.protocol.oidc.utils; + +import org.keycloak.protocol.oidc.mappers.AbstractPairwiseSubMapper; +import org.keycloak.protocol.oidc.mappers.PairwiseSubMapperHelper; +import org.keycloak.representations.idm.ClientRepresentation; +import org.keycloak.representations.idm.ProtocolMapperRepresentation; +import org.keycloak.services.ServicesLogger; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +public class PairwiseSubMapperUtils { + private static final ServicesLogger logger = ServicesLogger.ROOT_LOGGER; + + /** + * Returns a set of valid redirect URIs from the root url and redirect URIs registered on a client. + * + * @param clientRootUrl + * @param clientRedirectUris + * @return + */ + public static Set resolveValidRedirectUris(String clientRootUrl, Set clientRedirectUris) { + Set validRedirects = new HashSet(); + for (String redirectUri : clientRedirectUris) { + if (redirectUri.startsWith("/")) { + redirectUri = relativeToAbsoluteURI(clientRootUrl, redirectUri); + logger.debugv("replacing relative valid redirect with: {0}", redirectUri); + } + if (redirectUri != null) { + validRedirects.add(redirectUri); + } + } + return validRedirects.stream() + .filter(r -> r != null && !r.trim().isEmpty()) + .collect(Collectors.toSet()); + } + + /** + * Tries to resolve a valid sector identifier from a sector identifier URI. + * + * @param sectorIdentifierUri + * @return a sector identifier iff. the sector identifier URI is a valid URI, contains a valid scheme and contains a valid host component. + */ + public static String resolveValidSectorIdentifier(String sectorIdentifierUri) { + URI uri; + try { + uri = new URI(sectorIdentifierUri); + } catch (URISyntaxException e) { + logger.debug("Invalid sector identifier URI", e); + return null; + } + + if (uri.getScheme() == null) { + logger.debugv("Invalid sector identifier URI: {0}", sectorIdentifierUri); + return null; + } + + /*if (!uri.getScheme().equalsIgnoreCase("https")) { + logger.debugv("The sector identifier URI scheme must be HTTPS. Was '{0}'", uri.getScheme()); + return null; + }*/ + + if (uri.getHost() == null) { + logger.debug("The sector identifier URI must specify a host"); + return null; + } + + return uri.getHost(); + } + + /** + * Tries to resolve a valid sector identifier from the redirect URIs registered on a client. + * + * @param clientRootUrl Root url registered on the client. + * @param clientRedirectUris Redirect URIs registered on the client. + * @return a sector identifier iff. all the registered redirect URIs are located at the same host, otherwise {@code null}. + */ + public static String resolveValidSectorIdentifier(String clientRootUrl, Set clientRedirectUris) { + Set hosts = new HashSet<>(); + for (String redirectUri : resolveValidRedirectUris(clientRootUrl, clientRedirectUris)) { + try { + URI uri = new URI(redirectUri); + hosts.add(uri.getHost()); + } catch (URISyntaxException e) { + logger.debugv("client redirect uris contained an invalid uri: {0}", redirectUri); + } + } + if (hosts.isEmpty()) { + logger.debug("could not infer any valid sector_identifiers from client redirect uris"); + return null; + } + if (hosts.size() > 1) { + logger.debug("the client redirect uris contained multiple hosts"); + return null; + } + return hosts.iterator().next(); + } + + /** + * Checks if the the registered client redirect URIs matches the set of redirect URIs from the sector identifier URI. + * + * @param clientRootUrl root url registered on the client. + * @param clientRedirectUris redirect URIs registered on the client. + * @param sectorRedirects value of the sector identifier URI. + * @return {@code true} iff. the all the redirect URIs can be described by the {@code sectorRedirects}, i.e if the registered redirect URIs is a subset of the {@code sectorRedirects}, otherwise {@code false}. + */ + public static boolean matchesRedirects(String clientRootUrl, Set clientRedirectUris, Set sectorRedirects) { + Set validRedirects = resolveValidRedirectUris(clientRootUrl, clientRedirectUris); + for (String redirect : validRedirects) { + if (!matchesRedirect(sectorRedirects, redirect)) return false; + } + return true; + } + + private static boolean matchesRedirect(Set validRedirects, String redirect) { + for (String validRedirect : validRedirects) { + if (validRedirect.endsWith("*") && !validRedirect.contains("?")) { + // strip off the query component - we don't check them when wildcards are effective + String r = redirect.contains("?") ? redirect.substring(0, redirect.indexOf("?")) : redirect; + // strip off * + int length = validRedirect.length() - 1; + validRedirect = validRedirect.substring(0, length); + if (r.startsWith(validRedirect)) return true; + // strip off trailing '/' + if (length - 1 > 0 && validRedirect.charAt(length - 1) == '/') length--; + validRedirect = validRedirect.substring(0, length); + if (validRedirect.equals(r)) return true; + } else if (validRedirect.equals(redirect)) return true; + } + return false; + } + + private static String relativeToAbsoluteURI(String rootUrl, String relative) { + if (rootUrl == null || rootUrl.isEmpty()) { + return null; + } + relative = rootUrl + relative; + return relative; + } + + public static ProtocolMapperRepresentation getPairwiseSubMapperRepresentation(ClientRepresentation client) { + List mappers = client.getProtocolMappers(); + if (mappers == null) { + return null; + } + for (ProtocolMapperRepresentation mapper : mappers) { + if (mapper.getProtocolMapper().endsWith(AbstractPairwiseSubMapper.PROVIDER_ID_SUFFIX)) return mapper; + } + return null; + } + + public static String getSubjectIdentifierUri(ProtocolMapperRepresentation pairwiseMapper) { + return pairwiseMapper.getConfig().get(PairwiseSubMapperHelper.SECTOR_IDENTIFIER_URI); + } +} diff --git a/services/src/main/java/org/keycloak/protocol/oidc/utils/PairwiseSubMapperValidator.java b/services/src/main/java/org/keycloak/protocol/oidc/utils/PairwiseSubMapperValidator.java new file mode 100644 index 0000000000..e6d8a6eeb7 --- /dev/null +++ b/services/src/main/java/org/keycloak/protocol/oidc/utils/PairwiseSubMapperValidator.java @@ -0,0 +1,113 @@ +package org.keycloak.protocol.oidc.utils; + +import org.keycloak.connections.httpclient.HttpClientProvider; +import org.keycloak.models.ClientModel; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.ProtocolMapperModel; +import org.keycloak.protocol.ProtocolMapperConfigException; +import org.keycloak.protocol.oidc.mappers.PairwiseSubMapperHelper; +import org.keycloak.util.JsonSerialization; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * @author Martin Hardselius + */ +public class PairwiseSubMapperValidator { + + public static final String PAIRWISE_MALFORMED_CLIENT_REDIRECT_URI = "pairwiseMalformedClientRedirectURI"; + public static final String PAIRWISE_CLIENT_REDIRECT_URIS_MISSING_HOST = "pairwiseClientRedirectURIsMissingHost"; + public static final String PAIRWISE_CLIENT_REDIRECT_URIS_MULTIPLE_HOSTS = "pairwiseClientRedirectURIsMultipleHosts"; + public static final String PAIRWISE_MALFORMED_SECTOR_IDENTIFIER_URI = "pairwiseMalformedSectorIdentifierURI"; + public static final String PAIRWISE_FAILED_TO_GET_REDIRECT_URIS = "pairwiseFailedToGetRedirectURIs"; + public static final String PAIRWISE_REDIRECT_URIS_MISMATCH = "pairwiseRedirectURIsMismatch"; + + public static void validate(KeycloakSession session, ClientModel client, ProtocolMapperModel mapperModel) throws ProtocolMapperConfigException { + String sectorIdentifierUri = PairwiseSubMapperHelper.getSectorIdentifierUri(mapperModel); + String rootUrl = client.getRootUrl(); + Set redirectUris = client.getRedirectUris(); + validate(session, rootUrl, redirectUris, sectorIdentifierUri); + } + + public static void validate(KeycloakSession session, String rootUrl, Set redirectUris, String sectorIdentifierUri) throws ProtocolMapperConfigException { + if (sectorIdentifierUri == null || sectorIdentifierUri.isEmpty()) { + validateClientRedirectUris(rootUrl, redirectUris); + return; + } + validateSectorIdentifierUri(sectorIdentifierUri); + validateSectorIdentifierUri(session, rootUrl, redirectUris, sectorIdentifierUri); + } + + private static void validateClientRedirectUris(String rootUrl, Set redirectUris) throws ProtocolMapperConfigException { + Set hosts = new HashSet<>(); + for (String redirectUri : PairwiseSubMapperUtils.resolveValidRedirectUris(rootUrl, redirectUris)) { + try { + URI uri = new URI(redirectUri); + hosts.add(uri.getHost()); + } catch (URISyntaxException e) { + throw new ProtocolMapperConfigException("Client contained an invalid redirect URI.", + PAIRWISE_MALFORMED_CLIENT_REDIRECT_URI, e); + } + } + + if (hosts.isEmpty()) { + throw new ProtocolMapperConfigException("Client redirect URIs must contain a valid host component.", + PAIRWISE_CLIENT_REDIRECT_URIS_MISSING_HOST); + } + + if (hosts.size() > 1) { + throw new ProtocolMapperConfigException("Without a configured Sector Identifier URI, client redirect URIs must not contain multiple host components.", PAIRWISE_CLIENT_REDIRECT_URIS_MULTIPLE_HOSTS); + } + } + + private static void validateSectorIdentifierUri(String sectorIdentifierUri) throws ProtocolMapperConfigException { + URI uri; + try { + uri = new URI(sectorIdentifierUri); + } catch (URISyntaxException e) { + throw new ProtocolMapperConfigException("Invalid Sector Identifier URI.", + PAIRWISE_MALFORMED_SECTOR_IDENTIFIER_URI, e); + } + if (uri.getScheme() == null || uri.getHost() == null) { + throw new ProtocolMapperConfigException("Invalid Sector Identifier URI.", + PAIRWISE_MALFORMED_SECTOR_IDENTIFIER_URI); + } + } + + private static void validateSectorIdentifierUri(KeycloakSession session, String rootUrl, Set redirectUris, String sectorIdentifierUri) throws ProtocolMapperConfigException { + Set sectorRedirects = getSectorRedirects(session, sectorIdentifierUri); + if (!PairwiseSubMapperUtils.matchesRedirects(rootUrl, redirectUris, sectorRedirects)) { + throw new ProtocolMapperConfigException("Client redirect URIs does not match redirect URIs fetched from the Sector Identifier URI.", + PAIRWISE_REDIRECT_URIS_MISMATCH); + } + } + + private static Set getSectorRedirects(KeycloakSession session, String sectorIdentifierUri) throws ProtocolMapperConfigException { + InputStream is = null; + try { + is = session.getProvider(HttpClientProvider.class).get(sectorIdentifierUri); + List sectorRedirects = JsonSerialization.readValue(is, TypedList.class); + return new HashSet<>(sectorRedirects); + } catch (IOException e) { + throw new ProtocolMapperConfigException("Failed to get redirect URIs from the Sector Identifier URI.", + PAIRWISE_FAILED_TO_GET_REDIRECT_URIS, e); + } finally { + if (is != null) { + try { + is.close(); + } catch (IOException ignored) { + } + } + } + } + + public static class TypedList extends ArrayList {} + +} diff --git a/services/src/main/java/org/keycloak/protocol/oidc/utils/SubjectType.java b/services/src/main/java/org/keycloak/protocol/oidc/utils/SubjectType.java new file mode 100644 index 0000000000..ec1ba97a90 --- /dev/null +++ b/services/src/main/java/org/keycloak/protocol/oidc/utils/SubjectType.java @@ -0,0 +1,13 @@ +package org.keycloak.protocol.oidc.utils; + +public enum SubjectType { + PUBLIC, + PAIRWISE; + + public static SubjectType parse(String subjectTypeStr) { + if (subjectTypeStr == null) { + return PUBLIC; + } + return Enum.valueOf(SubjectType.class, subjectTypeStr.toUpperCase()); + } +} diff --git a/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java b/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java index 1fae787117..b1ae4ddac1 100644 --- a/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java +++ b/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java @@ -16,15 +16,18 @@ */ package org.keycloak.services; +import org.keycloak.credential.UserCredentialStore; +import org.keycloak.credential.UserCredentialStoreManager; import org.keycloak.models.*; import org.keycloak.models.cache.CacheRealmProvider; -import org.keycloak.models.cache.CacheUserProvider; +import org.keycloak.models.cache.UserCache; import org.keycloak.provider.Provider; import org.keycloak.provider.ProviderFactory; import org.keycloak.scripting.ScriptingProvider; import org.keycloak.storage.UserStorageManager; import org.keycloak.storage.federated.UserFederatedStorageProvider; +import javax.transaction.TransactionManager; import java.util.*; /** @@ -40,6 +43,7 @@ public class DefaultKeycloakSession implements KeycloakSession { private RealmProvider model; private UserProvider userModel; private UserStorageManager userStorageManager; + private UserCredentialStoreManager userCredentialStorageManager; private ScriptingProvider scriptingProvider; private UserSessionProvider sessionProvider; private UserFederationManager federationManager; @@ -48,7 +52,7 @@ public class DefaultKeycloakSession implements KeycloakSession { public DefaultKeycloakSession(DefaultKeycloakSessionFactory factory) { this.factory = factory; - this.transactionManager = new DefaultKeycloakTransactionManager(); + this.transactionManager = new DefaultKeycloakTransactionManager(this); federationManager = new UserFederationManager(this); context = new DefaultKeycloakContext(this); } @@ -68,7 +72,7 @@ public class DefaultKeycloakSession implements KeycloakSession { } private UserProvider getUserProvider() { - CacheUserProvider cache = getProvider(CacheUserProvider.class); + UserCache cache = getProvider(UserCache.class); if (cache != null) { return cache; } else { @@ -76,6 +80,12 @@ public class DefaultKeycloakSession implements KeycloakSession { } } + @Override + public UserCache getUserCache() { + return getProvider(UserCache.class); + + } + @Override public void enlistForClose(Provider provider) { closable.add(provider); @@ -125,6 +135,12 @@ public class DefaultKeycloakSession implements KeycloakSession { return userStorageManager; } + @Override + public UserCredentialManager userCredentialManager() { + if (userCredentialStorageManager == null) userCredentialStorageManager = new UserCredentialStoreManager(this); + return userCredentialStorageManager; + } + @Override public UserProvider userStorage() { if (userModel == null) { diff --git a/services/src/main/java/org/keycloak/services/DefaultKeycloakSessionFactory.java b/services/src/main/java/org/keycloak/services/DefaultKeycloakSessionFactory.java index 45bef3c3ea..36f9b7f163 100755 --- a/services/src/main/java/org/keycloak/services/DefaultKeycloakSessionFactory.java +++ b/services/src/main/java/org/keycloak/services/DefaultKeycloakSessionFactory.java @@ -20,6 +20,7 @@ import org.keycloak.Config; import org.keycloak.common.util.MultivaluedHashMap; import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSessionFactory; +import org.keycloak.provider.EnvironmentDependentProviderFactory; import org.keycloak.provider.Provider; import org.keycloak.provider.ProviderEvent; import org.keycloak.provider.ProviderEventListener; @@ -49,7 +50,6 @@ public class DefaultKeycloakSessionFactory implements KeycloakSessionFactory, Pr private Map, String> provider = new HashMap<>(); private volatile Map, Map> factoriesMap = new HashMap<>(); protected CopyOnWriteArrayList listeners = new CopyOnWriteArrayList<>(); - private TransactionManager tm; // TODO: Likely should be changed to int and use Time.currentTime() to be compatible with all our "time" reps protected long serverStartupTimestamp; @@ -97,8 +97,6 @@ public class DefaultKeycloakSessionFactory implements KeycloakSessionFactory, Pr // make the session factory ready for hot deployment ProviderManagerRegistry.SINGLETON.setDeployer(this); - JtaTransactionManagerLookup lookup = (JtaTransactionManagerLookup)getProviderFactory(JtaTransactionManagerLookup.class); - if (lookup != null) tm = lookup.getTransactionManager(); } protected Map, Map> getFactoriesCopy() { Map, Map> copy = new HashMap<>(); @@ -193,8 +191,7 @@ public class DefaultKeycloakSessionFactory implements KeycloakSessionFactory, Pr } Config.Scope scope = Config.scope(spi.getName(), provider); - if (scope.getBoolean("enabled", true)) { - + if (isEnabled(factory, scope)) { factory.init(scope); if (spi.isInternal() && !isInternal(factory)) { @@ -205,10 +202,11 @@ public class DefaultKeycloakSessionFactory implements KeycloakSessionFactory, Pr logger.debugv("Loaded SPI {0} (provider = {1})", spi.getName(), provider); } + } else { for (ProviderFactory factory : pm.load(spi)) { Config.Scope scope = Config.scope(spi.getName(), factory.getId()); - if (scope.getBoolean("enabled", true)) { + if (isEnabled(factory, scope)) { factory.init(scope); if (spi.isInternal() && !isInternal(factory)) { @@ -223,7 +221,16 @@ public class DefaultKeycloakSessionFactory implements KeycloakSessionFactory, Pr } } return factoryMap; + } + private boolean isEnabled(ProviderFactory factory, Config.Scope scope) { + if (!scope.getBoolean("enabled", true)) { + return false; + } + if (factory instanceof EnvironmentDependentProviderFactory) { + return ((EnvironmentDependentProviderFactory) factory).isSupported(); + } + return true; } protected void loadSPIs(ProviderManager pm, List spiList) { @@ -282,9 +289,6 @@ public class DefaultKeycloakSessionFactory implements KeycloakSessionFactory, Pr public KeycloakSession create() { KeycloakSession session = new DefaultKeycloakSession(this); - if (tm != null) { - session.getTransactionManager().enlist(new JtaTransactionWrapper(tm)); - } return session; } diff --git a/services/src/main/java/org/keycloak/services/DefaultKeycloakTransactionManager.java b/services/src/main/java/org/keycloak/services/DefaultKeycloakTransactionManager.java index 53f92f4fb8..81379a1cdf 100755 --- a/services/src/main/java/org/keycloak/services/DefaultKeycloakTransactionManager.java +++ b/services/src/main/java/org/keycloak/services/DefaultKeycloakTransactionManager.java @@ -16,9 +16,13 @@ */ package org.keycloak.services; +import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakTransaction; import org.keycloak.models.KeycloakTransactionManager; +import org.keycloak.transaction.JtaTransactionManagerLookup; +import org.keycloak.transaction.JtaTransactionWrapper; +import javax.transaction.TransactionManager; import java.util.LinkedList; import java.util.List; @@ -34,6 +38,12 @@ public class DefaultKeycloakTransactionManager implements KeycloakTransactionMan private List afterCompletion = new LinkedList(); private boolean active; private boolean rollback; + private KeycloakSession session; + private JTAPolicy jtaPolicy = JTAPolicy.REQUIRES_NEW; + + public DefaultKeycloakTransactionManager(KeycloakSession session) { + this.session = session; + } @Override public void enlist(KeycloakTransaction transaction) { @@ -62,12 +72,31 @@ public class DefaultKeycloakTransactionManager implements KeycloakTransactionMan prepare.add(transaction); } + @Override + public JTAPolicy getJTAPolicy() { + return jtaPolicy; + } + + @Override + public void setJTAPolicy(JTAPolicy policy) { + jtaPolicy = policy; + + } + @Override public void begin() { if (active) { throw new IllegalStateException("Transaction already active"); } + if (jtaPolicy == JTAPolicy.REQUIRES_NEW) { + JtaTransactionManagerLookup jtaLookup = session.getProvider(JtaTransactionManagerLookup.class); + TransactionManager tm = jtaLookup.getTransactionManager(); + if (tm != null) { + enlist(new JtaTransactionWrapper(tm)); + } + } + for (KeycloakTransaction tx : transactions) { tx.begin(); } diff --git a/services/src/main/java/org/keycloak/services/ServicesLogger.java b/services/src/main/java/org/keycloak/services/ServicesLogger.java index de7eef743f..0595abbcce 100644 --- a/services/src/main/java/org/keycloak/services/ServicesLogger.java +++ b/services/src/main/java/org/keycloak/services/ServicesLogger.java @@ -430,4 +430,12 @@ public interface ServicesLogger extends BasicLogger { @Message(id=96, value="Not found JWK of supported keyType under jwks_uri for usage: %s") void supportedJwkNotFound(String usage); + @LogMessage(level = WARN) + @Message(id=97, value="Invalid request") + void invalidRequest(@Cause Throwable t); + + @LogMessage(level = ERROR) + @Message(id=98, value="Failed to get redirect uris from sector identifier URI: %s") + void failedToGetRedirectUrisFromSectorIdentifierUri(@Cause Throwable t, String sectorIdentifierUri); + } diff --git a/services/src/main/java/org/keycloak/services/clientregistration/AbstractClientRegistrationProvider.java b/services/src/main/java/org/keycloak/services/clientregistration/AbstractClientRegistrationProvider.java index d634b006b7..f5ad8b073a 100755 --- a/services/src/main/java/org/keycloak/services/clientregistration/AbstractClientRegistrationProvider.java +++ b/services/src/main/java/org/keycloak/services/clientregistration/AbstractClientRegistrationProvider.java @@ -19,22 +19,22 @@ package org.keycloak.services.clientregistration; import org.keycloak.events.EventBuilder; import org.keycloak.events.EventType; -import org.keycloak.models.ClientInitialAccessModel; -import org.keycloak.models.ClientModel; -import org.keycloak.models.ClientRegistrationTrustedHostModel; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.ModelDuplicateException; +import org.keycloak.models.*; import org.keycloak.models.utils.ModelToRepresentation; import org.keycloak.models.utils.RepresentationToModel; +import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper; +import org.keycloak.protocol.oidc.mappers.AbstractPairwiseSubMapper; +import org.keycloak.protocol.oidc.mappers.PairwiseSubMapperHelper; +import org.keycloak.protocol.oidc.mappers.SHA265PairwiseSubMapper; +import org.keycloak.protocol.oidc.utils.SubjectType; import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.services.ErrorResponseException; import org.keycloak.services.ForbiddenException; -import org.keycloak.services.resources.admin.AdminRoot; import org.keycloak.services.validation.ClientValidator; +import org.keycloak.services.validation.PairwiseClientValidator; import org.keycloak.services.validation.ValidationMessages; import javax.ws.rs.core.Response; -import java.util.Properties; /** * @author Stian Thorgersen @@ -55,7 +55,7 @@ public abstract class AbstractClientRegistrationProvider implements ClientRegist auth.requireCreate(); ValidationMessages validationMessages = new ValidationMessages(); - if (!ClientValidator.validate(client, validationMessages)) { + if (!ClientValidator.validate(client, validationMessages) || !PairwiseClientValidator.validate(session, client, validationMessages)) { String errorCode = validationMessages.fieldHasError("redirectUris") ? ErrorCodes.INVALID_REDIRECT_URI : ErrorCodes.INVALID_CLIENT_METADATA; throw new ErrorResponseException( errorCode, @@ -66,6 +66,10 @@ public abstract class AbstractClientRegistrationProvider implements ClientRegist try { ClientModel clientModel = RepresentationToModel.createClient(session, session.getContext().getRealm(), client, true); + OIDCAdvancedConfigWrapper configWrapper = OIDCAdvancedConfigWrapper.fromClientRepresentation(client); + if (configWrapper.getSubjectType().equals(SubjectType.PAIRWISE)) { + addPairwiseSubMapper(clientModel, configWrapper.getSectorIdentifierUri()); + } client = ModelToRepresentation.toRepresentation(clientModel); @@ -119,7 +123,7 @@ public abstract class AbstractClientRegistrationProvider implements ClientRegist } ValidationMessages validationMessages = new ValidationMessages(); - if (!ClientValidator.validate(rep, validationMessages)) { + if (!ClientValidator.validate(rep, validationMessages) || !PairwiseClientValidator.validate(session, rep, validationMessages)) { String errorCode = validationMessages.fieldHasError("redirectUris") ? ErrorCodes.INVALID_REDIRECT_URI : ErrorCodes.INVALID_CLIENT_METADATA; throw new ErrorResponseException( errorCode, @@ -128,6 +132,7 @@ public abstract class AbstractClientRegistrationProvider implements ClientRegist ); } + updateSubjectType(rep, client); RepresentationToModel.updateClient(rep, client); rep = ModelToRepresentation.toRepresentation(client); @@ -140,6 +145,43 @@ public abstract class AbstractClientRegistrationProvider implements ClientRegist return rep; } + private void updateSubjectType(ClientRepresentation rep, ClientModel client) { + OIDCAdvancedConfigWrapper repConfigWrapper = OIDCAdvancedConfigWrapper.fromClientRepresentation(rep); + SubjectType repSubjectType = repConfigWrapper.getSubjectType(); + OIDCAdvancedConfigWrapper clientConfigWrapper = OIDCAdvancedConfigWrapper.fromClientModel(client); + SubjectType clientSubjectType = clientConfigWrapper.getSubjectType(); + + if (repSubjectType.equals(SubjectType.PAIRWISE) && clientSubjectType.equals(SubjectType.PAIRWISE)) { + updateSectorIdentifier(client, repConfigWrapper.getSectorIdentifierUri()); + } + + if (repSubjectType.equals(SubjectType.PAIRWISE) && clientSubjectType.equals(SubjectType.PUBLIC)) { + addPairwiseSubMapper(client, repConfigWrapper.getSectorIdentifierUri()); + } + + if (repSubjectType.equals(SubjectType.PUBLIC) && clientSubjectType.equals(SubjectType.PAIRWISE)) { + removePairwiseSubMapper(client); + } + } + + private void updateSectorIdentifier(ClientModel client, String sectorIdentifierUri) { + client.getProtocolMappers().stream().filter(mapping -> mapping.getProtocolMapper().endsWith(AbstractPairwiseSubMapper.PROVIDER_ID_SUFFIX)).forEach(mapping -> { + mapping.getConfig().put(PairwiseSubMapperHelper.SECTOR_IDENTIFIER_URI, sectorIdentifierUri); + }); + } + + private void addPairwiseSubMapper(ClientModel client, String sectorIdentifierUri) { + client.addProtocolMapper(SHA265PairwiseSubMapper.createPairwiseMapper(sectorIdentifierUri)); + } + + private void removePairwiseSubMapper(ClientModel client) { + for (ProtocolMapperModel mapping : client.getProtocolMappers()) { + if (mapping.getProtocolMapper().endsWith(AbstractPairwiseSubMapper.PROVIDER_ID_SUFFIX)) { + client.removeProtocolMapper(mapping); + } + } + } + public void delete(String clientId) { event.event(EventType.CLIENT_DELETE).client(clientId); diff --git a/services/src/main/java/org/keycloak/services/clientregistration/oidc/DescriptionConverter.java b/services/src/main/java/org/keycloak/services/clientregistration/oidc/DescriptionConverter.java index a95cc090d2..c377313ddc 100644 --- a/services/src/main/java/org/keycloak/services/clientregistration/oidc/DescriptionConverter.java +++ b/services/src/main/java/org/keycloak/services/clientregistration/oidc/DescriptionConverter.java @@ -32,6 +32,7 @@ import org.keycloak.protocol.oidc.OIDCLoginProtocol; import org.keycloak.protocol.oidc.utils.AuthorizeClientUtil; import org.keycloak.protocol.oidc.utils.JWKSUtils; import org.keycloak.protocol.oidc.utils.OIDCResponseType; +import org.keycloak.protocol.oidc.utils.SubjectType; import org.keycloak.representations.idm.CertificateRepresentation; import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.oidc.OIDCClientRepresentation; @@ -53,6 +54,7 @@ public class DescriptionConverter { public static ClientRepresentation toInternal(KeycloakSession session, OIDCClientRepresentation clientOIDC) throws ClientRegistrationException { ClientRepresentation client = new ClientRepresentation(); + client.setClientId(clientOIDC.getClientId()); client.setName(clientOIDC.getClientName()); client.setRedirectUris(clientOIDC.getRedirectUris()); @@ -89,14 +91,12 @@ public class DescriptionConverter { } client.setClientAuthenticatorType(clientAuthFactory.getId()); - // Externalize to ClientAuthenticator itself? - if (authMethod != null && authMethod.equals(OIDCLoginProtocol.PRIVATE_KEY_JWT)) { - - PublicKey publicKey = retrievePublicKey(clientOIDC); - if (publicKey == null) { - throw new ClientRegistrationException("Didn't find key of supported keyType for use " + JWK.Use.SIG.asString()); - } + PublicKey publicKey = retrievePublicKey(session, clientOIDC); + if (authMethod != null && authMethod.equals(OIDCLoginProtocol.PRIVATE_KEY_JWT) && publicKey == null) { + throw new ClientRegistrationException("Didn't find key of supported keyType for use " + JWK.Use.SIG.asString()); + } + if (publicKey != null) { String publicKeyPem = KeycloakModelUtils.getPemFromKey(publicKey); CertificateRepresentation rep = new CertificateRepresentation(); @@ -104,20 +104,30 @@ public class DescriptionConverter { CertificateInfoHelper.updateClientRepresentationCertificateInfo(client, rep, JWTClientAuthenticator.ATTR_PREFIX); } + OIDCAdvancedConfigWrapper configWrapper = OIDCAdvancedConfigWrapper.fromClientRepresentation(client); if (clientOIDC.getUserinfoSignedResponseAlg() != null) { - String userInfoSignedResponseAlg = clientOIDC.getUserinfoSignedResponseAlg(); - Algorithm algorithm = Enum.valueOf(Algorithm.class, userInfoSignedResponseAlg); + Algorithm algorithm = Enum.valueOf(Algorithm.class, clientOIDC.getUserinfoSignedResponseAlg()); + configWrapper.setUserInfoSignedResponseAlg(algorithm); + } - OIDCAdvancedConfigWrapper.fromClientRepresentation(client).setUserInfoSignedResponseAlg(algorithm); + if (clientOIDC.getRequestObjectSigningAlg() != null) { + Algorithm algorithm = Enum.valueOf(Algorithm.class, clientOIDC.getRequestObjectSigningAlg()); + configWrapper.setRequestObjectSignatureAlg(algorithm); + } + + SubjectType subjectType = SubjectType.parse(clientOIDC.getSubjectType()); + configWrapper.setSubjectType(subjectType); + if (subjectType.equals(SubjectType.PAIRWISE)) { + configWrapper.setSectorIdentifierUri(clientOIDC.getSectorIdentifierUri()); } return client; } - private static PublicKey retrievePublicKey(OIDCClientRepresentation clientOIDC) { + private static PublicKey retrievePublicKey(KeycloakSession session, OIDCClientRepresentation clientOIDC) { if (clientOIDC.getJwksUri() == null && clientOIDC.getJwks() == null) { - throw new ClientRegistrationException("Requested client authentication method '%s' but jwks_uri nor jwks were available in config"); + return null; } if (clientOIDC.getJwksUri() != null && clientOIDC.getJwks() != null) { @@ -129,7 +139,7 @@ public class DescriptionConverter { keySet = clientOIDC.getJwks(); } else { try { - keySet = JWKSUtils.sendJwksRequest(clientOIDC.getJwksUri()); + keySet = JWKSUtils.sendJwksRequest(session, clientOIDC.getJwksUri()); } catch (IOException ioe) { throw new ClientRegistrationException("Failed to send JWKS request to specified jwks_uri", ioe); } @@ -166,6 +176,14 @@ public class DescriptionConverter { if (config.isUserInfoSignatureRequired()) { response.setUserinfoSignedResponseAlg(config.getUserInfoSignedResponseAlg().toString()); } + if (config.getRequestObjectSignatureAlg() != null) { + response.setRequestObjectSigningAlg(config.getRequestObjectSignatureAlg().toString()); + } + + response.setSubjectType(config.getSubjectType().toString().toLowerCase()); + if (config.getSubjectType().equals(SubjectType.PAIRWISE)) { + response.setSectorIdentifierUri(config.getSectorIdentifierUri()); + } return response; } diff --git a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java index e53ac00b9f..aa536f5fca 100755 --- a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java +++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java @@ -408,7 +408,7 @@ public class AuthenticationManager { // refresh the cookies! createLoginCookie(session, realm, userSession.getUser(), userSession, uriInfo, clientConnection); if (userSession.getState() != UserSessionModel.State.LOGGED_IN) userSession.setState(UserSessionModel.State.LOGGED_IN); - if (userSession.isRememberMe()) createRememberMeCookie(realm, userSession.getUser().getUsername(), uriInfo, clientConnection); + if (userSession.isRememberMe()) createRememberMeCookie(realm, userSession.getLoginUsername(), uriInfo, clientConnection); // Update userSession note with authTime. But just if flag SSO_AUTH is not set if (!isSSOAuthentication(clientSession)) { diff --git a/services/src/main/java/org/keycloak/services/managers/ClientManager.java b/services/src/main/java/org/keycloak/services/managers/ClientManager.java index 7bdf5b1889..3de62db240 100644 --- a/services/src/main/java/org/keycloak/services/managers/ClientManager.java +++ b/services/src/main/java/org/keycloak/services/managers/ClientManager.java @@ -265,7 +265,6 @@ public class ClientManager { InstallationAdapterConfig rep = new InstallationAdapterConfig(); rep.setAuthServerUrl(baseUri.toString()); rep.setRealm(realmModel.getName()); - rep.setRealmKey(realmModel.getPublicKeyPem()); rep.setSslRequired(realmModel.getSslRequired().name().toLowerCase()); if (clientModel.isPublicClient() && !clientModel.isBearerOnly()) rep.setPublicClient(true); @@ -286,7 +285,6 @@ public class ClientManager { StringBuffer buffer = new StringBuffer(); buffer.append("\n"); buffer.append(" ").append(realmModel.getName()).append("\n"); - buffer.append(" ").append(realmModel.getPublicKeyPem()).append("\n"); buffer.append(" ").append(baseUri.toString()).append("\n"); if (clientModel.isBearerOnly()){ buffer.append(" true\n"); diff --git a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java index 6cdf52eab2..68e080663b 100644 --- a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java +++ b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java @@ -45,10 +45,13 @@ import org.keycloak.services.scheduled.ClusterAwareScheduledTaskRunner; import org.keycloak.services.util.JsonConfigProvider; import org.keycloak.services.util.ObjectMapperResolver; import org.keycloak.timer.TimerProvider; +import org.keycloak.transaction.JtaTransactionManagerLookup; import org.keycloak.util.JsonSerialization; import org.keycloak.common.util.SystemEnvProperties; import javax.servlet.ServletContext; +import javax.transaction.SystemException; +import javax.transaction.Transaction; import javax.ws.rs.core.Application; import javax.ws.rs.core.Context; import javax.ws.rs.core.UriInfo; @@ -155,11 +158,28 @@ public class KeycloakApplication extends Application { // Migrate model, bootstrap master realm, import realms and create admin user. This is done with acquired dbLock protected ExportImportManager migrateAndBootstrap() { ExportImportManager exportImportManager; + logger.debug("Calling migrateModel"); migrateModel(); + logger.debug("bootstrap"); KeycloakSession session = sessionFactory.create(); try { session.getTransactionManager().begin(); + JtaTransactionManagerLookup lookup = (JtaTransactionManagerLookup) sessionFactory.getProviderFactory(JtaTransactionManagerLookup.class); + if (lookup != null) { + if (lookup.getTransactionManager() != null) { + try { + Transaction transaction = lookup.getTransactionManager().getTransaction(); + logger.debugv("bootstrap current transaction? {0}", transaction != null); + if (transaction != null) { + logger.debugv("bootstrap current transaction status? {0}", transaction.getStatus()); + } + } catch (SystemException e) { + throw new RuntimeException(e); + } + } + } + ApplianceBootstrap applianceBootstrap = new ApplianceBootstrap(session); exportImportManager = new ExportImportManager(session); diff --git a/services/src/main/java/org/keycloak/services/resources/RealmsResource.java b/services/src/main/java/org/keycloak/services/resources/RealmsResource.java index 1e42e7b740..d0ca449de0 100755 --- a/services/src/main/java/org/keycloak/services/resources/RealmsResource.java +++ b/services/src/main/java/org/keycloak/services/resources/RealmsResource.java @@ -35,6 +35,7 @@ import org.keycloak.services.managers.RealmManager; import org.keycloak.services.resource.RealmResourceProvider; import org.keycloak.services.util.CacheControlUtil; import org.keycloak.services.util.ResolveRelative; +import org.keycloak.utils.ProfileHelper; import org.keycloak.wellknown.WellKnownProvider; import javax.ws.rs.GET; @@ -254,6 +255,8 @@ public class RealmsResource { @Path("{realm}/authz") public Object getAuthorizationService(@PathParam("realm") String name) { + ProfileHelper.requirePreview(); + init(name); AuthorizationProvider authorization = this.session.getProvider(AuthorizationProvider.class); AuthorizationService service = new AuthorizationService(authorization); diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java index dd17b07a8e..955dff0144 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java @@ -18,9 +18,9 @@ package org.keycloak.services.resources.admin; import org.jboss.resteasy.annotations.cache.NoCache; import org.jboss.resteasy.spi.BadRequestException; -import org.jboss.resteasy.spi.NotFoundException; import org.jboss.resteasy.spi.ResteasyProviderFactory; import org.keycloak.authorization.admin.AuthorizationService; +import org.keycloak.common.Profile; import org.keycloak.events.admin.OperationType; import org.keycloak.events.admin.ResourceType; import org.keycloak.models.ClientModel; @@ -50,11 +50,14 @@ import org.keycloak.services.resources.KeycloakApplication; import org.keycloak.services.ErrorResponse; import org.keycloak.common.util.Time; import org.keycloak.services.validation.ClientValidator; +import org.keycloak.services.validation.PairwiseClientValidator; import org.keycloak.services.validation.ValidationMessages; +import org.keycloak.utils.ProfileHelper; import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; import javax.ws.rs.GET; +import javax.ws.rs.NotFoundException; import javax.ws.rs.POST; import javax.ws.rs.PUT; import javax.ws.rs.Path; @@ -107,7 +110,7 @@ public class ClientResource { @Path("protocol-mappers") public ProtocolMappersResource getProtocolMappers() { - ProtocolMappersResource mappers = new ProtocolMappersResource(client, auth, adminEvent); + ProtocolMappersResource mappers = new ProtocolMappersResource(realm, client, auth, adminEvent); ResteasyProviderFactory.getInstance().injectProperties(mappers); return mappers; } @@ -127,7 +130,7 @@ public class ClientResource { } ValidationMessages validationMessages = new ValidationMessages(); - if (!ClientValidator.validate(rep, validationMessages)) { + if (!ClientValidator.validate(rep, validationMessages) || !PairwiseClientValidator.validate(session, rep, validationMessages)) { Properties messages = AdminRoot.getMessages(session, realm, auth.getAuth().getToken().getLocale()); throw new ErrorResponseException( validationMessages.getStringMessages(), @@ -152,10 +155,12 @@ public class ClientResource { RepresentationToModel.updateClient(rep, client); - if (TRUE.equals(rep.getAuthorizationServicesEnabled())) { - authorization().enable(); - } else { - authorization().disable(); + if (Profile.isPreviewEnabled()) { + if (TRUE.equals(rep.getAuthorizationServicesEnabled())) { + authorization().enable(); + } else { + authorization().disable(); + } } } @@ -176,7 +181,9 @@ public class ClientResource { ClientRepresentation representation = ModelToRepresentation.toRepresentation(client); - representation.setAuthorizationServicesEnabled(authorization().isEnabled()); + if (Profile.isPreviewEnabled()) { + representation.setAuthorizationServicesEnabled(authorization().isEnabled()); + } return representation; } @@ -561,6 +568,8 @@ public class ClientResource { @Path("/authz") public AuthorizationService authorization() { + ProfileHelper.requirePreview(); + AuthorizationService resource = new AuthorizationService(this.session, this.client, this.auth); ResteasyProviderFactory.getInstance().injectProperties(resource); diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientTemplateResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientTemplateResource.java index baf9bb65f3..761e307f9c 100644 --- a/services/src/main/java/org/keycloak/services/resources/admin/ClientTemplateResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientTemplateResource.java @@ -81,7 +81,7 @@ public class ClientTemplateResource { @Path("protocol-mappers") public ProtocolMappersResource getProtocolMappers() { - ProtocolMappersResource mappers = new ProtocolMappersResource(template, auth, adminEvent); + ProtocolMappersResource mappers = new ProtocolMappersResource(realm, template, auth, adminEvent); ResteasyProviderFactory.getInstance().injectProperties(mappers); return mappers; } diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientsResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientsResource.java index dacea26b68..1be0cac65d 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/ClientsResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientsResource.java @@ -31,6 +31,7 @@ import org.keycloak.services.ErrorResponseException; import org.keycloak.services.ServicesLogger; import org.keycloak.services.managers.ClientManager; import org.keycloak.services.validation.ClientValidator; +import org.keycloak.services.validation.PairwiseClientValidator; import org.keycloak.services.validation.ValidationMessages; import javax.ws.rs.*; @@ -120,7 +121,7 @@ public class ClientsResource { auth.requireManage(); ValidationMessages validationMessages = new ValidationMessages(); - if (!ClientValidator.validate(rep, validationMessages)) { + if (!ClientValidator.validate(rep, validationMessages) || !PairwiseClientValidator.validate(session, rep, validationMessages)) { Properties messages = AdminRoot.getMessages(session, realm, auth.getAuth().getToken().getLocale()); throw new ErrorResponseException( validationMessages.getStringMessages(), diff --git a/services/src/main/java/org/keycloak/services/resources/admin/IdentityProvidersResource.java b/services/src/main/java/org/keycloak/services/resources/admin/IdentityProvidersResource.java index 958a8494e0..8e7c9acb69 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/IdentityProvidersResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/IdentityProvidersResource.java @@ -116,7 +116,7 @@ public class IdentityProvidersResource { InputPart file = formDataMap.get("file").get(0); InputStream inputStream = file.getBody(InputStream.class, null); IdentityProviderFactory providerFactory = getProviderFactorytById(providerId); - Map config = providerFactory.parseConfig(inputStream); + Map config = providerFactory.parseConfig(session, inputStream); return config; } @@ -143,7 +143,7 @@ public class IdentityProvidersResource { try { IdentityProviderFactory providerFactory = getProviderFactorytById(providerId); Map config; - config = providerFactory.parseConfig(inputStream); + config = providerFactory.parseConfig(session, inputStream); return config; } finally { try { diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ProtocolMappersResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ProtocolMappersResource.java index b9da2bf107..804082051d 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/ProtocolMappersResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/ProtocolMappersResource.java @@ -20,32 +20,26 @@ import org.jboss.resteasy.annotations.cache.NoCache; import org.jboss.resteasy.spi.NotFoundException; import org.keycloak.events.admin.OperationType; import org.keycloak.events.admin.ResourceType; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.ModelDuplicateException; -import org.keycloak.models.ProtocolMapperContainerModel; -import org.keycloak.models.ProtocolMapperModel; +import org.keycloak.models.*; import org.keycloak.models.utils.ModelToRepresentation; import org.keycloak.models.utils.RepresentationToModel; +import org.keycloak.protocol.ProtocolMapper; +import org.keycloak.protocol.ProtocolMapperConfigException; import org.keycloak.representations.idm.ProtocolMapperRepresentation; import org.keycloak.services.ErrorResponse; +import org.keycloak.services.ErrorResponseException; import org.keycloak.services.ServicesLogger; import org.keycloak.services.resources.admin.RealmAuth.Resource; -import javax.ws.rs.Consumes; -import javax.ws.rs.DELETE; -import javax.ws.rs.GET; -import javax.ws.rs.POST; -import javax.ws.rs.PUT; -import javax.ws.rs.Path; -import javax.ws.rs.PathParam; -import javax.ws.rs.Produces; +import javax.ws.rs.*; import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.UriInfo; - +import java.text.MessageFormat; import java.util.LinkedList; import java.util.List; +import java.util.Properties; /** * Base resource for managing users @@ -56,6 +50,8 @@ import java.util.List; public class ProtocolMappersResource { protected static final ServicesLogger logger = ServicesLogger.ROOT_LOGGER; + protected RealmModel realm; + protected ProtocolMapperContainerModel client; protected RealmAuth auth; @@ -68,7 +64,8 @@ public class ProtocolMappersResource { @Context protected KeycloakSession session; - public ProtocolMappersResource(ProtocolMapperContainerModel client, RealmAuth auth, AdminEventBuilder adminEvent) { + public ProtocolMappersResource(RealmModel realm, ProtocolMapperContainerModel client, RealmAuth auth, AdminEventBuilder adminEvent) { + this.realm = realm; this.auth = auth; this.client = client; this.adminEvent = adminEvent.resource(ResourceType.PROTOCOL_MAPPER); @@ -119,6 +116,7 @@ public class ProtocolMappersResource { ProtocolMapperModel model = null; try { model = RepresentationToModel.toModel(rep); + validateModel(model); model = client.addProtocolMapper(model); adminEvent.operation(OperationType.CREATE).resourcePath(uriInfo, model.getId()).representation(rep).success(); @@ -146,6 +144,7 @@ public class ProtocolMappersResource { ProtocolMapperModel model = null; for (ProtocolMapperRepresentation rep : reps) { model = RepresentationToModel.toModel(rep); + validateModel(model); model = client.addProtocolMapper(model); } adminEvent.operation(OperationType.CREATE).resourcePath(uriInfo).representation(reps).success(); @@ -216,6 +215,9 @@ public class ProtocolMappersResource { ProtocolMapperModel model = client.getProtocolMapperById(id); if (model == null) throw new NotFoundException("Model not found"); model = RepresentationToModel.toModel(rep); + + validateModel(model); + client.updateProtocolMapper(model); adminEvent.operation(OperationType.UPDATE).resourcePath(uriInfo).representation(rep).success(); } @@ -242,4 +244,18 @@ public class ProtocolMappersResource { } + private void validateModel(ProtocolMapperModel model) { + try { + ProtocolMapper mapper = (ProtocolMapper)session.getKeycloakSessionFactory().getProviderFactory(ProtocolMapper.class, model.getProtocolMapper()); + if (mapper != null) { + mapper.validateConfig(session, realm, client, model); + } + } catch (ProtocolMapperConfigException ex) { + logger.error(ex.getMessage()); + Properties messages = AdminRoot.getMessages(session, realm, auth.getAuth().getToken().getLocale()); + throw new ErrorResponseException(ex.getMessage(), MessageFormat.format(messages.getProperty(ex.getMessageKey(), ex.getMessage()), ex.getParameters()), + Response.Status.BAD_REQUEST); + } + } + } diff --git a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java index 4fd941b728..1caa5fb207 100644 --- a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java @@ -34,9 +34,8 @@ import org.keycloak.events.admin.OperationType; import org.keycloak.events.admin.ResourceType; import org.keycloak.exportimport.ClientDescriptionConverter; import org.keycloak.exportimport.ClientDescriptionConverterFactory; -import org.keycloak.jose.jws.JWSBuilder; -import org.keycloak.jose.jws.JWSInput; import org.keycloak.models.ClientModel; +import org.keycloak.models.Constants; import org.keycloak.models.GroupModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.ModelDuplicateException; @@ -44,7 +43,7 @@ import org.keycloak.models.RealmModel; import org.keycloak.models.UserFederationProviderModel; import org.keycloak.models.UserSessionModel; import org.keycloak.models.cache.CacheRealmProvider; -import org.keycloak.models.cache.CacheUserProvider; +import org.keycloak.models.cache.UserCache; import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.models.utils.ModelToRepresentation; import org.keycloak.models.utils.RepresentationToModel; @@ -55,7 +54,6 @@ import org.keycloak.representations.idm.AdminEventRepresentation; import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.EventRepresentation; import org.keycloak.representations.idm.GroupRepresentation; -import org.keycloak.representations.idm.IdentityProviderRepresentation; import org.keycloak.representations.idm.PartialImportRepresentation; import org.keycloak.representations.idm.RealmEventsConfigRepresentation; import org.keycloak.representations.idm.RealmRepresentation; @@ -67,7 +65,6 @@ import org.keycloak.services.ServicesLogger; import org.keycloak.services.managers.UsersSyncManager; import org.keycloak.services.ErrorResponse; import org.keycloak.services.resources.admin.RealmAuth.Resource; -import org.keycloak.timer.TimerProvider; import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; @@ -85,8 +82,6 @@ import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; import javax.ws.rs.core.UriInfo; -import java.security.PrivateKey; -import java.security.PublicKey; import java.security.cert.X509Certificate; import java.text.ParseException; import java.text.SimpleDateFormat; @@ -287,7 +282,7 @@ public class RealmAdminResource { logger.debug("updating realm: " + realm.getName()); try { - if (!"GENERATE".equals(rep.getPublicKey()) && (rep.getPrivateKey() != null && rep.getPublicKey() != null)) { + if (!Constants.GENERATE.equals(rep.getPublicKey()) && (rep.getPrivateKey() != null && rep.getPublicKey() != null)) { try { KeyPairVerifier.verify(rep.getPrivateKey(), rep.getPublicKey()); } catch (VerificationException e) { @@ -295,7 +290,7 @@ public class RealmAdminResource { } } - if (!"GENERATE".equals(rep.getPublicKey()) && (rep.getCertificate() != null)) { + if (!Constants.GENERATE.equals(rep.getPublicKey()) && (rep.getCertificate() != null)) { try { X509Certificate cert = PemUtils.decodeCertificate(rep.getCertificate()); if (cert == null) { @@ -866,7 +861,7 @@ public class RealmAdminResource { public void clearUserCache() { auth.requireManage(); - CacheUserProvider cache = session.getProvider(CacheUserProvider.class); + UserCache cache = session.getProvider(UserCache.class); if (cache != null) { cache.clear(); } diff --git a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java index 3986988f87..26daf84019 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java @@ -184,6 +184,8 @@ public class UsersResource { return ErrorResponse.exists("User is read only!"); } catch (ModelException me) { return ErrorResponse.exists("Could not update user!"); + } catch (Exception me) { // JPA may be committed by JTA which can't + return ErrorResponse.exists("Could not update user!"); } } diff --git a/services/src/main/java/org/keycloak/services/resources/admin/info/ServerInfoAdminResource.java b/services/src/main/java/org/keycloak/services/resources/admin/info/ServerInfoAdminResource.java index a7195e12c7..638e6570d4 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/info/ServerInfoAdminResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/info/ServerInfoAdminResource.java @@ -43,6 +43,7 @@ import org.keycloak.policy.PasswordPolicyProviderFactory; import org.keycloak.provider.*; import org.keycloak.representations.idm.ComponentTypeRepresentation; import org.keycloak.representations.idm.PasswordPolicyTypeRepresentation; +import org.keycloak.representations.info.ProfileInfoRepresentation; import org.keycloak.theme.Theme; import org.keycloak.theme.ThemeProvider; import org.keycloak.models.KeycloakSession; @@ -84,6 +85,7 @@ public class ServerInfoAdminResource { ServerInfoRepresentation info = new ServerInfoRepresentation(); info.setSystemInfo(SystemInfoRepresentation.create(session.getKeycloakSessionFactory().getServerStartupTimestamp())); info.setMemoryInfo(MemoryInfoRepresentation.create()); + info.setProfileInfo(ProfileInfoRepresentation.create()); setSocialProviders(info); setIdentityProviders(info); diff --git a/services/src/main/java/org/keycloak/services/util/CertificateInfoHelper.java b/services/src/main/java/org/keycloak/services/util/CertificateInfoHelper.java index b309927f81..359d28d84b 100644 --- a/services/src/main/java/org/keycloak/services/util/CertificateInfoHelper.java +++ b/services/src/main/java/org/keycloak/services/util/CertificateInfoHelper.java @@ -17,9 +17,18 @@ package org.keycloak.services.util; +import java.security.PublicKey; +import java.security.cert.X509Certificate; import java.util.HashMap; +import javax.ws.rs.core.Response; + +import org.keycloak.authentication.AuthenticationFlowError; +import org.keycloak.authentication.ClientAuthenticationFlowContext; +import org.keycloak.authentication.authenticators.client.ClientAuthUtil; import org.keycloak.models.ClientModel; +import org.keycloak.models.ModelException; +import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.representations.idm.CertificateRepresentation; import org.keycloak.representations.idm.ClientRepresentation; @@ -34,6 +43,8 @@ public class CertificateInfoHelper { public static final String PUBLIC_KEY = "public.key"; + // CLIENT MODEL METHODS + public static CertificateRepresentation getCertificateFromClient(ClientModel client, String attributePrefix) { String privateKeyAttribute = attributePrefix + "." + PRIVATE_KEY; String certificateAttribute = attributePrefix + "." + X509CERTIFICATE; @@ -75,6 +86,32 @@ public class CertificateInfoHelper { } + public static PublicKey getSignatureValidationKey(ClientModel client, String attributePrefix) throws ModelException { + CertificateRepresentation certInfo = getCertificateFromClient(client, attributePrefix); + + String encodedCertificate = certInfo.getCertificate(); + String encodedPublicKey = certInfo.getPublicKey(); + + if (encodedCertificate == null && encodedPublicKey == null) { + throw new ModelException("Client doesn't have certificate or publicKey configured"); + } + + if (encodedCertificate != null && encodedPublicKey != null) { + throw new ModelException("Client has both publicKey and certificate configured"); + } + + // TODO: Caching of publicKeys / certificates, so it doesn't need to be always computed from pem. For performance reasons... + if (encodedCertificate != null) { + X509Certificate clientCert = KeycloakModelUtils.getCertificate(encodedCertificate); + return clientCert.getPublicKey(); + } else { + return KeycloakModelUtils.getPublicKey(encodedPublicKey); + } + } + + + // CLIENT REPRESENTATION METHODS + public static void updateClientRepresentationCertificateInfo(ClientRepresentation client, CertificateRepresentation rep, String attributePrefix) { String privateKeyAttribute = attributePrefix + "." + PRIVATE_KEY; String certificateAttribute = attributePrefix + "." + X509CERTIFICATE; diff --git a/services/src/main/java/org/keycloak/services/validation/PairwiseClientValidator.java b/services/src/main/java/org/keycloak/services/validation/PairwiseClientValidator.java new file mode 100644 index 0000000000..6cc20334e1 --- /dev/null +++ b/services/src/main/java/org/keycloak/services/validation/PairwiseClientValidator.java @@ -0,0 +1,41 @@ +package org.keycloak.services.validation; + +import org.keycloak.models.KeycloakSession; +import org.keycloak.protocol.ProtocolMapperConfigException; +import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper; +import org.keycloak.protocol.oidc.utils.PairwiseSubMapperValidator; +import org.keycloak.protocol.oidc.utils.SubjectType; +import org.keycloak.representations.idm.ClientRepresentation; + +import java.util.HashSet; +import java.util.Set; + + +/** + * @author Martin Hardselius + */ +public class PairwiseClientValidator { + + public static boolean validate(KeycloakSession session, ClientRepresentation client, ValidationMessages messages) { + OIDCAdvancedConfigWrapper configWrapper = OIDCAdvancedConfigWrapper.fromClientRepresentation(client); + if (configWrapper.getSubjectType().equals(SubjectType.PAIRWISE)) { + String sectorIdentifierUri = configWrapper.getSectorIdentifierUri(); + String rootUrl = client.getRootUrl(); + Set redirectUris = new HashSet<>(); + if (client.getRedirectUris() != null) redirectUris.addAll(client.getRedirectUris()); + return validate(session, rootUrl, redirectUris, sectorIdentifierUri, messages); + } + return true; + } + + public static boolean validate(KeycloakSession session, String rootUrl, Set redirectUris, String sectorIdentifierUri, ValidationMessages messages) { + try { + PairwiseSubMapperValidator.validate(session, rootUrl, redirectUris, sectorIdentifierUri); + } catch (ProtocolMapperConfigException e) { + messages.add(e.getMessage(), e.getMessageKey()); + return false; + } + return true; + } + +} diff --git a/services/src/main/java/org/keycloak/services/validation/ValidationMessages.java b/services/src/main/java/org/keycloak/services/validation/ValidationMessages.java index e26ebff397..a7e82c8601 100644 --- a/services/src/main/java/org/keycloak/services/validation/ValidationMessages.java +++ b/services/src/main/java/org/keycloak/services/validation/ValidationMessages.java @@ -57,8 +57,11 @@ public class ValidationMessages { } public boolean fieldHasError(String fieldId) { + if (fieldId == null) { + return false; + } for (ValidationMessage message : messages) { - if (message.getFieldId().equals(fieldId)) { + if (fieldId.equals(message.getFieldId())) { return true; } } diff --git a/server-spi/src/main/java/org/keycloak/storage/UserStorageManager.java b/services/src/main/java/org/keycloak/storage/UserStorageManager.java similarity index 84% rename from server-spi/src/main/java/org/keycloak/storage/UserStorageManager.java rename to services/src/main/java/org/keycloak/storage/UserStorageManager.java index 1a1fca543c..226a083fd8 100755 --- a/server-spi/src/main/java/org/keycloak/storage/UserStorageManager.java +++ b/services/src/main/java/org/keycloak/storage/UserStorageManager.java @@ -30,6 +30,8 @@ import org.keycloak.models.ProtocolMapperModel; import org.keycloak.models.RealmModel; import org.keycloak.models.RoleModel; import org.keycloak.models.UserConsentModel; +import org.keycloak.models.cache.CachedUserModel; +import org.keycloak.models.cache.OnUserCache; import org.keycloak.storage.user.UserCredentialAuthenticationProvider; import org.keycloak.models.UserCredentialModel; import org.keycloak.storage.user.UserCredentialValidatorProvider; @@ -56,7 +58,7 @@ import java.util.Set; * @author Bill Burke * @version $Revision: 1 $ */ -public class UserStorageManager implements UserProvider { +public class UserStorageManager implements UserProvider, OnUserCache { private static final Logger logger = Logger.getLogger(UserStorageManager.class); @@ -70,22 +72,22 @@ public class UserStorageManager implements UserProvider { return session.userLocalStorage(); } - protected List getStorageProviders(RealmModel realm) { + public static List getStorageProviders(RealmModel realm) { return realm.getUserStorageProviders(); } - protected T getFirstStorageProvider(RealmModel realm, Class type) { + public static T getFirstStorageProvider(KeycloakSession session, RealmModel realm, Class type) { for (UserStorageProviderModel model : getStorageProviders(realm)) { UserStorageProviderFactory factory = (UserStorageProviderFactory)session.getKeycloakSessionFactory().getProviderFactory(UserStorageProvider.class, model.getProviderId()); if (Types.supports(type, factory, UserStorageProviderFactory.class)) { - return type.cast(getStorageProviderInstance(model, factory)); + return type.cast(getStorageProviderInstance(session, model, factory)); } } return null; } - private UserStorageProvider getStorageProviderInstance(UserStorageProviderModel model, UserStorageProviderFactory factory) { + public static UserStorageProvider getStorageProviderInstance(KeycloakSession session, UserStorageProviderModel model, UserStorageProviderFactory factory) { UserStorageProvider instance = (UserStorageProvider)session.getAttribute(model.getId()); if (instance != null) return instance; instance = factory.create(session, model); @@ -95,12 +97,12 @@ public class UserStorageManager implements UserProvider { } - protected List getStorageProviders(RealmModel realm, Class type) { + public static List getStorageProviders(KeycloakSession session, RealmModel realm, Class type) { List list = new LinkedList<>(); for (UserStorageProviderModel model : getStorageProviders(realm)) { UserStorageProviderFactory factory = (UserStorageProviderFactory) session.getKeycloakSessionFactory().getProviderFactory(UserStorageProvider.class, model.getProviderId()); if (Types.supports(type, factory, UserStorageProviderFactory.class)) { - list.add(type.cast(getStorageProviderInstance(model, factory))); + list.add(type.cast(getStorageProviderInstance(session, model, factory))); } @@ -116,31 +118,31 @@ public class UserStorageManager implements UserProvider { @Override public UserModel addUser(RealmModel realm, String username) { - UserRegistrationProvider registry = getFirstStorageProvider(realm, UserRegistrationProvider.class); + UserRegistrationProvider registry = getFirstStorageProvider(session, realm, UserRegistrationProvider.class); if (registry != null) { return registry.addUser(realm, username); } return localStorage().addUser(realm, username.toLowerCase()); } - public UserStorageProvider getStorageProvider(RealmModel realm, String componentId) { + public static UserStorageProvider getStorageProvider(KeycloakSession session, RealmModel realm, String componentId) { ComponentModel model = realm.getComponent(componentId); if (model == null) return null; UserStorageProviderFactory factory = (UserStorageProviderFactory)session.getKeycloakSessionFactory().getProviderFactory(UserStorageProvider.class, model.getProviderId()); if (factory == null) { throw new ModelException("Could not find UserStorageProviderFactory for: " + model.getProviderId()); } - return getStorageProviderInstance(new UserStorageProviderModel(model), factory); + return getStorageProviderInstance(session, new UserStorageProviderModel(model), factory); } @Override public boolean removeUser(RealmModel realm, UserModel user) { - getFederatedStorage().preRemove(realm, user); + if (getFederatedStorage() != null) getFederatedStorage().preRemove(realm, user); StorageId storageId = new StorageId(user.getId()); if (storageId.getProviderId() == null) { return localStorage().removeUser(realm, user); } - UserRegistrationProvider registry = (UserRegistrationProvider)getStorageProvider(realm, storageId.getProviderId()); + UserRegistrationProvider registry = (UserRegistrationProvider)getStorageProvider(session, realm, storageId.getProviderId()); if (registry == null) { throw new ModelException("Could not resolve StorageProvider: " + storageId.getProviderId()); } @@ -233,7 +235,7 @@ public class UserStorageManager implements UserProvider { if (storageId.getProviderId() == null) { return localStorage().getUserById(id, realm); } - UserLookupProvider provider = (UserLookupProvider)getStorageProvider(realm, storageId.getProviderId()); + UserLookupProvider provider = (UserLookupProvider)getStorageProvider(session, realm, storageId.getProviderId()); return provider.getUserById(id, realm); } @@ -246,7 +248,7 @@ public class UserStorageManager implements UserProvider { public UserModel getUserByUsername(String username, RealmModel realm) { UserModel user = localStorage().getUserByUsername(username, realm); if (user != null) return user; - for (UserLookupProvider provider : getStorageProviders(realm, UserLookupProvider.class)) { + for (UserLookupProvider provider : getStorageProviders(session, realm, UserLookupProvider.class)) { user = provider.getUserByUsername(username, realm); if (user != null) return user; } @@ -257,7 +259,7 @@ public class UserStorageManager implements UserProvider { public UserModel getUserByEmail(String email, RealmModel realm) { UserModel user = localStorage().getUserByEmail(email, realm); if (user != null) return user; - for (UserLookupProvider provider : getStorageProviders(realm, UserLookupProvider.class)) { + for (UserLookupProvider provider : getStorageProviders(session, realm, UserLookupProvider.class)) { user = provider.getUserByEmail(email, realm); if (user != null) return user; } @@ -270,6 +272,7 @@ public class UserStorageManager implements UserProvider { if (user != null) { return user; } + if (getFederatedStorage() == null) return null; String id = getFederatedStorage().getUserByFederatedIdentity(socialLink, realm); if (id != null) return getUserById(id, realm); return null; @@ -299,7 +302,7 @@ public class UserStorageManager implements UserProvider { @Override public int getUsersCount(RealmModel realm) { int size = localStorage().getUsersCount(realm); - for (UserQueryProvider provider : getStorageProviders(realm, UserQueryProvider.class)) { + for (UserQueryProvider provider : getStorageProviders(session, realm, UserQueryProvider.class)) { size += provider.getUsersCount(realm); } return size; @@ -313,9 +316,7 @@ public class UserStorageManager implements UserProvider { protected List query(PaginatedQuery pagedQuery, RealmModel realm, int firstResult, int maxResults) { if (maxResults == 0) return Collections.EMPTY_LIST; - - - List storageProviders = getStorageProviders(realm, UserQueryProvider.class); + List storageProviders = getStorageProviders(session, realm, UserQueryProvider.class); // we can skip rest of method if there are no storage providers if (storageProviders.isEmpty()) { return pagedQuery.query(localStorage(), firstResult, maxResults); @@ -324,7 +325,7 @@ public class UserStorageManager implements UserProvider { List results = new LinkedList(); providers.add(localStorage()); providers.addAll(storageProviders); - providers.add(getFederatedStorage()); + if (getFederatedStorage() != null) providers.add(getFederatedStorage()); int leftToRead = maxResults; int leftToFirstResult = firstResult; @@ -430,7 +431,7 @@ public class UserStorageManager implements UserProvider { if (StorageId.isLocalStorage(user)) { set.addAll(localStorage().getFederatedIdentities(user, realm)); } - set.addAll(getFederatedStorage().getFederatedIdentities(user, realm)); + if (getFederatedStorage() != null) set.addAll(getFederatedStorage().getFederatedIdentities(user, realm)); return set; } @@ -441,13 +442,14 @@ public class UserStorageManager implements UserProvider { FederatedIdentityModel model = localStorage().getFederatedIdentity(user, socialProvider, realm); if (model != null) return model; } - return getFederatedStorage().getFederatedIdentity(user, socialProvider, realm); + if (getFederatedStorage() != null) return getFederatedStorage().getFederatedIdentity(user, socialProvider, realm); + else return null; } @Override public void grantToAllUsers(RealmModel realm, RoleModel role) { // not federation-aware for now - List storageProviders = getStorageProviders(realm, UserRegistrationProvider.class); + List storageProviders = getStorageProviders(session, realm, UserRegistrationProvider.class); LinkedList providers = new LinkedList<>(); providers.add(localStorage()); providers.addAll(storageProviders); @@ -481,47 +483,53 @@ public class UserStorageManager implements UserProvider { @Override public void preRemove(RealmModel realm) { localStorage().preRemove(realm); - getFederatedStorage().preRemove(realm); - for (UserStorageProvider provider : getStorageProviders(realm, UserStorageProvider.class)) { - provider.preRemove(realm); + if (getFederatedStorage() != null) { + getFederatedStorage().preRemove(realm); + for (UserStorageProvider provider : getStorageProviders(session, realm, UserStorageProvider.class)) { + provider.preRemove(realm); + } } } @Override public void preRemove(RealmModel realm, UserFederationProviderModel model) { - getFederatedStorage().preRemove(realm, model); + if (getFederatedStorage() != null) getFederatedStorage().preRemove(realm, model); localStorage().preRemove(realm, model); } @Override public void preRemove(RealmModel realm, GroupModel group) { localStorage().preRemove(realm, group); - getFederatedStorage().preRemove(realm, group); - for (UserStorageProvider provider : getStorageProviders(realm, UserStorageProvider.class)) { - provider.preRemove(realm, group); + if (getFederatedStorage() != null) { + getFederatedStorage().preRemove(realm, group); + for (UserStorageProvider provider : getStorageProviders(session, realm, UserStorageProvider.class)) { + provider.preRemove(realm, group); + } } } @Override public void preRemove(RealmModel realm, RoleModel role) { localStorage().preRemove(realm, role); - getFederatedStorage().preRemove(realm, role); - for (UserStorageProvider provider : getStorageProviders(realm, UserStorageProvider.class)) { - provider.preRemove(realm, role); + if (getFederatedStorage() != null) { + getFederatedStorage().preRemove(realm, role); + for (UserStorageProvider provider : getStorageProviders(session, realm, UserStorageProvider.class)) { + provider.preRemove(realm, role); + } } } @Override public void preRemove(RealmModel realm, ClientModel client) { localStorage().preRemove(realm, client); - getFederatedStorage().preRemove(realm, client); + if (getFederatedStorage() != null) getFederatedStorage().preRemove(realm, client); } @Override public void preRemove(ProtocolMapperModel protocolMapper) { localStorage().preRemove(protocolMapper); - getFederatedStorage().preRemove(protocolMapper); + if (getFederatedStorage() != null) getFederatedStorage().preRemove(protocolMapper); } @Override @@ -558,7 +566,7 @@ public class UserStorageManager implements UserProvider { if (toValidate.isEmpty()) return true; - UserStorageProvider provider = getStorageProvider(realm, StorageId.resolveProviderId(user)); + UserStorageProvider provider = getStorageProvider(session, realm, StorageId.resolveProviderId(user)); if (!(provider instanceof UserCredentialValidatorProvider)) { return false; } @@ -572,7 +580,7 @@ public class UserStorageManager implements UserProvider { @Override public CredentialValidationOutput validCredentials(KeycloakSession session, RealmModel realm, UserCredentialModel... input) { - List providers = getStorageProviders(realm, UserCredentialAuthenticationProvider.class); + List providers = getStorageProviders(session, realm, UserCredentialAuthenticationProvider.class); if (providers.isEmpty()) return CredentialValidationOutput.failed(); CredentialValidationOutput result = null; @@ -606,7 +614,23 @@ public class UserStorageManager implements UserProvider { } + @Override + public void onCache(RealmModel realm, CachedUserModel user) { + if (StorageId.isLocalStorage(user)) { + if (session.userLocalStorage() instanceof OnUserCache) { + ((OnUserCache)session.userLocalStorage()).onCache(realm, user); + } + } else { + Object provider = getStorageProvider(session, realm, StorageId.resolveProviderId(user)); + if (provider != null && provider instanceof OnUserCache) { + ((OnUserCache)provider).onCache(realm, user); + } + } + } + @Override public void close() { } + + } diff --git a/services/src/main/java/org/keycloak/transaction/JtaTransactionWrapper.java b/services/src/main/java/org/keycloak/transaction/JtaTransactionWrapper.java index ecc3071a0d..e387ff4599 100644 --- a/services/src/main/java/org/keycloak/transaction/JtaTransactionWrapper.java +++ b/services/src/main/java/org/keycloak/transaction/JtaTransactionWrapper.java @@ -16,7 +16,9 @@ */ package org.keycloak.transaction; +import org.jboss.logging.Logger; import org.keycloak.models.KeycloakTransaction; +import org.keycloak.storage.UserStorageManager; import javax.transaction.InvalidTransactionException; import javax.transaction.NotSupportedException; @@ -31,16 +33,22 @@ import javax.transaction.UserTransaction; * @version $Revision: 1 $ */ public class JtaTransactionWrapper implements KeycloakTransaction { + private static final Logger logger = Logger.getLogger(JtaTransactionWrapper.class); protected TransactionManager tm; protected Transaction ut; protected Transaction suspended; + protected Exception ended; public JtaTransactionWrapper(TransactionManager tm) { this.tm = tm; try { + suspended = tm.suspend(); + logger.debug("new JtaTransactionWrapper"); + logger.debugv("was existing? {0}", suspended != null); tm.begin(); ut = tm.getTransaction(); + //ended = new Exception(); } catch (Exception e) { throw new RuntimeException(e); } @@ -53,16 +61,20 @@ public class JtaTransactionWrapper implements KeycloakTransaction { @Override public void commit() { try { - ut.commit(); + logger.debug("JtaTransactionWrapper commit"); + tm.commit(); } catch (Exception e) { throw new RuntimeException(e); + } finally { + end(); } } @Override public void rollback() { try { - ut.rollback(); + logger.debug("JtaTransactionWrapper rollback"); + tm.rollback(); } catch (Exception e) { throw new RuntimeException(e); } finally { @@ -74,7 +86,7 @@ public class JtaTransactionWrapper implements KeycloakTransaction { @Override public void setRollbackOnly() { try { - ut.setRollbackOnly(); + tm.setRollbackOnly(); } catch (Exception e) { throw new RuntimeException(e); } @@ -83,7 +95,7 @@ public class JtaTransactionWrapper implements KeycloakTransaction { @Override public boolean getRollbackOnly() { try { - return ut.getStatus() == Status.STATUS_MARKED_ROLLBACK; + return tm.getStatus() == Status.STATUS_MARKED_ROLLBACK; } catch (Exception e) { throw new RuntimeException(e); } @@ -92,15 +104,28 @@ public class JtaTransactionWrapper implements KeycloakTransaction { @Override public boolean isActive() { try { - return ut.getStatus() == Status.STATUS_ACTIVE; + return tm.getStatus() == Status.STATUS_ACTIVE; } catch (Exception e) { throw new RuntimeException(e); } } + /* + + @Override + protected void finalize() throws Throwable { + if (ended != null) { + logger.error("TX didn't close at position", ended); + } + + } + */ protected void end() { + ended = null; + logger.debug("JtaTransactionWrapper end"); if (suspended != null) { try { + logger.debug("JtaTransactionWrapper resuming suspended"); tm.resume(suspended); } catch (Exception e) { throw new RuntimeException(e); diff --git a/services/src/main/java/org/keycloak/utils/ProfileHelper.java b/services/src/main/java/org/keycloak/utils/ProfileHelper.java new file mode 100644 index 0000000000..719bd24e19 --- /dev/null +++ b/services/src/main/java/org/keycloak/utils/ProfileHelper.java @@ -0,0 +1,36 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.keycloak.utils; + +import org.keycloak.common.Profile; + +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.Response; + +/** + * @author Stian Thorgersen + */ +public class ProfileHelper { + + public static void requirePreview() { + if (!Profile.isPreviewEnabled()) { + throw new WebApplicationException("Feature not available in current profile", Response.Status.NOT_IMPLEMENTED); + } + } + +} diff --git a/services/src/main/resources/META-INF/services/org.keycloak.authentication.AuthenticatorFactory b/services/src/main/resources/META-INF/services/org.keycloak.authentication.AuthenticatorFactory index 97c92994fb..fa7ee28eea 100755 --- a/services/src/main/resources/META-INF/services/org.keycloak.authentication.AuthenticatorFactory +++ b/services/src/main/resources/META-INF/services/org.keycloak.authentication.AuthenticatorFactory @@ -20,6 +20,7 @@ org.keycloak.authentication.authenticators.browser.UsernamePasswordFormFactory org.keycloak.authentication.authenticators.browser.OTPFormAuthenticatorFactory org.keycloak.authentication.authenticators.browser.ScriptBasedAuthenticatorFactory org.keycloak.authentication.authenticators.browser.SpnegoAuthenticatorFactory +org.keycloak.authentication.authenticators.browser.IdentityProviderAuthenticatorFactory org.keycloak.authentication.authenticators.directgrant.ValidateOTP org.keycloak.authentication.authenticators.directgrant.ValidatePassword org.keycloak.authentication.authenticators.directgrant.ValidateUsername @@ -33,4 +34,4 @@ org.keycloak.authentication.authenticators.broker.IdpConfirmLinkAuthenticatorFac org.keycloak.authentication.authenticators.broker.IdpEmailVerificationAuthenticatorFactory org.keycloak.authentication.authenticators.broker.IdpUsernamePasswordFormFactory org.keycloak.authentication.authenticators.browser.ConditionalOtpFormAuthenticatorFactory -org.keycloak.protocol.saml.profile.ecp.authenticator.HttpBasicAuthenticator +org.keycloak.protocol.saml.profile.ecp.authenticator.HttpBasicAuthenticator \ No newline at end of file diff --git a/services/src/main/resources/META-INF/services/org.keycloak.protocol.ProtocolMapper b/services/src/main/resources/META-INF/services/org.keycloak.protocol.ProtocolMapper index 9d1e10e927..77830dc995 100755 --- a/services/src/main/resources/META-INF/services/org.keycloak.protocol.ProtocolMapper +++ b/services/src/main/resources/META-INF/services/org.keycloak.protocol.ProtocolMapper @@ -34,5 +34,5 @@ org.keycloak.protocol.saml.mappers.UserSessionNoteStatementMapper org.keycloak.protocol.saml.mappers.GroupMembershipMapper org.keycloak.protocol.oidc.mappers.UserClientRoleMappingMapper org.keycloak.protocol.oidc.mappers.UserRealmRoleMappingMapper - +org.keycloak.protocol.oidc.mappers.SHA265PairwiseSubMapper diff --git a/services/src/main/resources/META-INF/services/org.keycloak.provider.Spi b/services/src/main/resources/META-INF/services/org.keycloak.provider.Spi index 77cba5e1ef..55b31a09f9 100755 --- a/services/src/main/resources/META-INF/services/org.keycloak.provider.Spi +++ b/services/src/main/resources/META-INF/services/org.keycloak.provider.Spi @@ -18,5 +18,4 @@ org.keycloak.exportimport.ClientDescriptionConverterSpi org.keycloak.wellknown.WellKnownSpi org.keycloak.services.clientregistration.ClientRegistrationSpi -org.keycloak.transaction.TransactionManagerLookupSpi diff --git a/testsuite/integration-arquillian/pom.xml b/testsuite/integration-arquillian/pom.xml index 977fba877c..7bada0cd38 100644 --- a/testsuite/integration-arquillian/pom.xml +++ b/testsuite/integration-arquillian/pom.xml @@ -113,12 +113,17 @@ xml-maven-plugin 1.0.1 + + maven-resources-plugin + 3.0.0 + test-apps + test-utils servers tests diff --git a/testsuite/integration-arquillian/servers/auth-server/jboss/common/datasource-jdbc-url.xsl b/testsuite/integration-arquillian/servers/auth-server/jboss/common/datasource-jdbc-url.xsl index 589ee4cc1d..445b9733ff 100644 --- a/testsuite/integration-arquillian/servers/auth-server/jboss/common/datasource-jdbc-url.xsl +++ b/testsuite/integration-arquillian/servers/auth-server/jboss/common/datasource-jdbc-url.xsl @@ -1,11 +1,7 @@ + exclude-result-prefixes="xalan"> diff --git a/testsuite/integration-arquillian/servers/auth-server/jboss/common/datasource.xsl b/testsuite/integration-arquillian/servers/auth-server/jboss/common/datasource.xsl index e42a21c725..bf199cb88a 100644 --- a/testsuite/integration-arquillian/servers/auth-server/jboss/common/datasource.xsl +++ b/testsuite/integration-arquillian/servers/auth-server/jboss/common/datasource.xsl @@ -17,12 +17,8 @@ + exclude-result-prefixes="xalan"> diff --git a/testsuite/integration-arquillian/servers/auth-server/jboss/common/keycloak-server-subsystem.xsl b/testsuite/integration-arquillian/servers/auth-server/jboss/common/keycloak-server-subsystem.xsl index f32c036600..d104e37cc8 100644 --- a/testsuite/integration-arquillian/servers/auth-server/jboss/common/keycloak-server-subsystem.xsl +++ b/testsuite/integration-arquillian/servers/auth-server/jboss/common/keycloak-server-subsystem.xsl @@ -36,9 +36,14 @@ + + + org.keycloak.testsuite.integration-arquillian-testsuite-providers + + - - + + @@ -46,6 +51,14 @@ module:org.keycloak.testsuite.integration-arquillian-testsuite-providers + + + + + + + + diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestApplicationResourceProvider.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestApplicationResourceProvider.java index bc63c99f70..5f392a0bc4 100644 --- a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestApplicationResourceProvider.java +++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestApplicationResourceProvider.java @@ -27,6 +27,8 @@ import org.keycloak.representations.adapters.action.PushNotBeforeAction; import org.keycloak.representations.adapters.action.TestAvailabilityAction; import org.keycloak.services.resource.RealmResourceProvider; import org.keycloak.services.resources.RealmsResource; +import org.keycloak.testsuite.rest.resource.TestingExportImportResource; +import org.keycloak.testsuite.rest.resource.TestingOIDCEndpointsApplicationResource; import javax.ws.rs.Consumes; import javax.ws.rs.GET; @@ -53,14 +55,16 @@ public class TestApplicationResourceProvider implements RealmResourceProvider { private final BlockingQueue adminLogoutActions; private final BlockingQueue adminPushNotBeforeActions; private final BlockingQueue adminTestAvailabilityAction; + private final TestApplicationResourceProviderFactory.OIDCClientData oidcClientData; public TestApplicationResourceProvider(KeycloakSession session, BlockingQueue adminLogoutActions, BlockingQueue adminPushNotBeforeActions, - BlockingQueue adminTestAvailabilityAction) { + BlockingQueue adminTestAvailabilityAction, TestApplicationResourceProviderFactory.OIDCClientData oidcClientData) { this.session = session; this.adminLogoutActions = adminLogoutActions; this.adminPushNotBeforeActions = adminPushNotBeforeActions; this.adminTestAvailabilityAction = adminTestAvailabilityAction; + this.oidcClientData = oidcClientData; } @POST @@ -164,6 +168,11 @@ public class TestApplicationResourceProvider implements RealmResourceProvider { return sb.toString(); } + @Path("/oidc-client-endpoints") + public TestingOIDCEndpointsApplicationResource getTestingOIDCClientEndpoints() { + return new TestingOIDCEndpointsApplicationResource(oidcClientData); + } + @Override public Object getResource() { return this; diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestApplicationResourceProviderFactory.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestApplicationResourceProviderFactory.java index 98ca2baa74..d8d2a8d163 100644 --- a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestApplicationResourceProviderFactory.java +++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestApplicationResourceProviderFactory.java @@ -18,16 +18,16 @@ package org.keycloak.testsuite.rest; import org.keycloak.Config.Scope; -import org.keycloak.events.admin.AdminEvent; import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSessionFactory; -import org.keycloak.representations.adapters.action.AdminAction; import org.keycloak.representations.adapters.action.LogoutAction; import org.keycloak.representations.adapters.action.PushNotBeforeAction; import org.keycloak.representations.adapters.action.TestAvailabilityAction; import org.keycloak.services.resource.RealmResourceProvider; import org.keycloak.services.resource.RealmResourceProviderFactory; +import java.security.KeyPair; +import java.util.List; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingDeque; @@ -40,9 +40,11 @@ public class TestApplicationResourceProviderFactory implements RealmResourceProv private BlockingQueue pushNotBeforeActions = new LinkedBlockingDeque<>(); private BlockingQueue testAvailabilityActions = new LinkedBlockingDeque<>(); + private final OIDCClientData oidcClientData = new OIDCClientData(); + @Override public RealmResourceProvider create(KeycloakSession session) { - return new TestApplicationResourceProvider(session, adminLogoutActions, pushNotBeforeActions, testAvailabilityActions); + return new TestApplicationResourceProvider(session, adminLogoutActions, pushNotBeforeActions, testAvailabilityActions, oidcClientData); } @Override @@ -62,4 +64,35 @@ public class TestApplicationResourceProviderFactory implements RealmResourceProv return "app"; } + + public static class OIDCClientData { + + private KeyPair signingKeyPair; + private String oidcRequest; + private List sectorIdentifierRedirectUris; + + public KeyPair getSigningKeyPair() { + return signingKeyPair; + } + + public void setSigningKeyPair(KeyPair signingKeyPair) { + this.signingKeyPair = signingKeyPair; + } + + public String getOidcRequest() { + return oidcRequest; + } + + public void setOidcRequest(String oidcRequest) { + this.oidcRequest = oidcRequest; + } + + public List getSectorIdentifierRedirectUris() { + return sectorIdentifierRedirectUris; + } + + public void setSectorIdentifierRedirectUris(List sectorIdentifierRedirectUris) { + this.sectorIdentifierRedirectUris = sectorIdentifierRedirectUris; + } + } } diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestingResourceProvider.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestingResourceProvider.java index b658d35c3a..2085cfac14 100644 --- a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestingResourceProvider.java +++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestingResourceProvider.java @@ -23,15 +23,20 @@ import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import org.infinispan.Cache; +import org.keycloak.OAuth2Constants; import org.keycloak.common.util.Time; import org.keycloak.connections.infinispan.InfinispanConnectionProvider; import org.keycloak.events.Event; import org.keycloak.events.admin.AdminEvent; import org.keycloak.events.admin.ResourceType; +import org.keycloak.jose.jws.Algorithm; +import org.keycloak.jose.jws.JWSBuilder; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; import org.keycloak.models.UserSessionModel; import org.keycloak.models.utils.ModelToRepresentation; +import org.keycloak.protocol.oidc.OIDCLoginProtocol; +import org.keycloak.protocol.oidc.endpoints.request.AuthorizationEndpointRequest; import org.keycloak.representations.idm.AdminEventRepresentation; import org.keycloak.representations.idm.EventRepresentation; import org.keycloak.services.managers.ClientSessionCode; @@ -76,6 +81,7 @@ import org.keycloak.models.UserProvider; import org.keycloak.representations.idm.AuthDetailsRepresentation; import org.keycloak.representations.idm.AuthenticationFlowRepresentation; import org.keycloak.representations.idm.UserRepresentation; +import org.keycloak.testsuite.rest.resource.TestingExportImportResource; import static org.keycloak.exportimport.ExportImportConfig.*; @@ -564,22 +570,6 @@ public class TestingResourceProvider implements RealmResourceProvider { return result; } - @GET - @Path("/run-import") - @Produces(MediaType.APPLICATION_JSON) - public Response runImport() { - new ExportImportManager(session).runImport(); - return Response.ok().build(); - } - - @GET - @Path("/run-export") - @Produces(MediaType.APPLICATION_JSON) - public Response runExport() { - new ExportImportManager(session).runExport(); - return Response.ok().build(); - } - @GET @Path("/valid-credentials") @Produces(MediaType.APPLICATION_JSON) @@ -647,83 +637,14 @@ public class TestingResourceProvider implements RealmResourceProvider { return ModelToRepresentation.toRepresentation(user); } + @Path("/export-import") + public TestingExportImportResource getExportImportResource() { + return new TestingExportImportResource(session); + } + private RealmModel getRealmByName(String realmName) { RealmProvider realmProvider = session.getProvider(RealmProvider.class); return realmProvider.getRealmByName(realmName); } - @GET - @Path("/get-users-per-file") - @Consumes(MediaType.APPLICATION_JSON) - @Produces(MediaType.APPLICATION_JSON) - public Integer getUsersPerFile() { - String usersPerFile = System.getProperty(USERS_PER_FILE, String.valueOf(DEFAULT_USERS_PER_FILE)); - return Integer.parseInt(usersPerFile.trim()); - } - - @PUT - @Path("/set-users-per-file") - @Consumes(MediaType.APPLICATION_JSON) - public void setUsersPerFile(@QueryParam("usersPerFile") Integer usersPerFile) { - System.setProperty(USERS_PER_FILE, String.valueOf(usersPerFile)); - } - - @GET - @Path("/get-dir") - @Consumes(MediaType.APPLICATION_JSON) - @Produces(MediaType.APPLICATION_JSON) - public String getDir() { - return System.getProperty(DIR); - } - - @PUT - @Path("/set-dir") - @Consumes(MediaType.APPLICATION_JSON) - @Produces(MediaType.APPLICATION_JSON) - public String setDir(@QueryParam("dir") String dir) { - return System.setProperty(DIR, dir); - } - - @PUT - @Path("/export-import-provider") - @Consumes(MediaType.APPLICATION_JSON) - public void setProvider(@QueryParam("exportImportProvider") String exportImportProvider) { - System.setProperty(PROVIDER, exportImportProvider); - } - - @PUT - @Path("/export-import-file") - @Consumes(MediaType.APPLICATION_JSON) - public void setFile(@QueryParam("file") String file) { - System.setProperty(FILE, file); - } - - @PUT - @Path("/export-import-action") - @Consumes(MediaType.APPLICATION_JSON) - public void setAction(@QueryParam("exportImportAction") String exportImportAction) { - System.setProperty(ACTION, exportImportAction); - } - - @PUT - @Path("/set-realm-name") - @Consumes(MediaType.APPLICATION_JSON) - public void setRealmName(@QueryParam("realmName") String realmName) { - if (realmName != null && !realmName.isEmpty()) { - System.setProperty(REALM_NAME, realmName); - } else { - System.getProperties().remove(REALM_NAME); - } - } - - @GET - @Path("/get-test-dir") - @Consumes(MediaType.APPLICATION_JSON) - @Produces(MediaType.APPLICATION_JSON) - public String getExportImportTestDirectory() { - System.setProperty("project.build.directory", "target"); - String absolutePath = new File(System.getProperty("project.build.directory", "target")).getAbsolutePath(); - return absolutePath; - } - } diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/resource/TestingExportImportResource.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/resource/TestingExportImportResource.java new file mode 100644 index 0000000000..4f9151c753 --- /dev/null +++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/resource/TestingExportImportResource.java @@ -0,0 +1,142 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.keycloak.testsuite.rest.resource; + +import java.io.File; + +import javax.ws.rs.Consumes; +import javax.ws.rs.GET; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +import org.keycloak.exportimport.ExportImportManager; +import org.keycloak.models.KeycloakSession; + +import static org.keycloak.exportimport.ExportImportConfig.ACTION; +import static org.keycloak.exportimport.ExportImportConfig.DEFAULT_USERS_PER_FILE; +import static org.keycloak.exportimport.ExportImportConfig.DIR; +import static org.keycloak.exportimport.ExportImportConfig.FILE; +import static org.keycloak.exportimport.ExportImportConfig.PROVIDER; +import static org.keycloak.exportimport.ExportImportConfig.REALM_NAME; +import static org.keycloak.exportimport.ExportImportConfig.USERS_PER_FILE; + +/** + * @author Marek Posolda + */ +public class TestingExportImportResource { + + private final KeycloakSession session; + + public TestingExportImportResource(KeycloakSession session) { + this.session = session; + } + + @GET + @Path("/run-import") + @Produces(MediaType.APPLICATION_JSON) + public Response runImport() { + new ExportImportManager(session).runImport(); + return Response.ok().build(); + } + + @GET + @Path("/run-export") + @Produces(MediaType.APPLICATION_JSON) + public Response runExport() { + new ExportImportManager(session).runExport(); + return Response.ok().build(); + } + + @GET + @Path("/get-users-per-file") + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public Integer getUsersPerFile() { + String usersPerFile = System.getProperty(USERS_PER_FILE, String.valueOf(DEFAULT_USERS_PER_FILE)); + return Integer.parseInt(usersPerFile.trim()); + } + + @PUT + @Path("/set-users-per-file") + @Consumes(MediaType.APPLICATION_JSON) + public void setUsersPerFile(@QueryParam("usersPerFile") Integer usersPerFile) { + System.setProperty(USERS_PER_FILE, String.valueOf(usersPerFile)); + } + + @GET + @Path("/get-dir") + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public String getDir() { + return System.getProperty(DIR); + } + + @PUT + @Path("/set-dir") + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public String setDir(@QueryParam("dir") String dir) { + return System.setProperty(DIR, dir); + } + + @PUT + @Path("/export-import-provider") + @Consumes(MediaType.APPLICATION_JSON) + public void setProvider(@QueryParam("exportImportProvider") String exportImportProvider) { + System.setProperty(PROVIDER, exportImportProvider); + } + + @PUT + @Path("/export-import-file") + @Consumes(MediaType.APPLICATION_JSON) + public void setFile(@QueryParam("file") String file) { + System.setProperty(FILE, file); + } + + @PUT + @Path("/export-import-action") + @Consumes(MediaType.APPLICATION_JSON) + public void setAction(@QueryParam("exportImportAction") String exportImportAction) { + System.setProperty(ACTION, exportImportAction); + } + + @PUT + @Path("/set-realm-name") + @Consumes(MediaType.APPLICATION_JSON) + public void setRealmName(@QueryParam("realmName") String realmName) { + if (realmName != null && !realmName.isEmpty()) { + System.setProperty(REALM_NAME, realmName); + } else { + System.getProperties().remove(REALM_NAME); + } + } + + @GET + @Path("/get-test-dir") + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public String getExportImportTestDirectory() { + System.setProperty("project.build.directory", "target"); + String absolutePath = new File(System.getProperty("project.build.directory", "target")).getAbsolutePath(); + return absolutePath; + } +} diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/resource/TestingOIDCEndpointsApplicationResource.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/resource/TestingOIDCEndpointsApplicationResource.java new file mode 100644 index 0000000000..8b5df6ceb4 --- /dev/null +++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/resource/TestingOIDCEndpointsApplicationResource.java @@ -0,0 +1,152 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.keycloak.testsuite.rest.resource; + +import org.jboss.resteasy.annotations.cache.NoCache; +import org.jboss.resteasy.spi.BadRequestException; +import org.keycloak.OAuth2Constants; +import org.keycloak.jose.jwk.JSONWebKeySet; +import org.keycloak.jose.jwk.JWK; +import org.keycloak.jose.jwk.JWKBuilder; +import org.keycloak.jose.jws.Algorithm; +import org.keycloak.jose.jws.JWSBuilder; +import org.keycloak.models.utils.KeycloakModelUtils; +import org.keycloak.protocol.oidc.OIDCLoginProtocol; +import org.keycloak.testsuite.rest.TestApplicationResourceProviderFactory; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.MediaType; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * @author Marek Posolda + */ +public class TestingOIDCEndpointsApplicationResource { + + public static final String PRIVATE_KEY = "privateKey"; + public static final String PUBLIC_KEY = "publicKey"; + + private final TestApplicationResourceProviderFactory.OIDCClientData clientData; + + public TestingOIDCEndpointsApplicationResource(TestApplicationResourceProviderFactory.OIDCClientData oidcClientData) { + this.clientData = oidcClientData; + } + + @GET + @Produces(MediaType.APPLICATION_JSON) + @Path("/generate-keys") + @NoCache + public Map generateKeys() { + try { + KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA"); + generator.initialize(2048); + clientData.setSigningKeyPair(generator.generateKeyPair()); + } catch (NoSuchAlgorithmException e) { + throw new BadRequestException("Error generating signing keypair", e); + } + + String privateKeyPem = KeycloakModelUtils.getPemFromKey(clientData.getSigningKeyPair().getPrivate()); + String publicKeyPem = KeycloakModelUtils.getPemFromKey(clientData.getSigningKeyPair().getPublic()); + + Map res = new HashMap<>(); + res.put(PRIVATE_KEY, privateKeyPem); + res.put(PUBLIC_KEY, publicKeyPem); + return res; + } + + + @GET + @Produces(MediaType.APPLICATION_JSON) + @Path("/get-jwks") + @NoCache + public JSONWebKeySet getJwks() { + JSONWebKeySet keySet = new JSONWebKeySet(); + + if (clientData.getSigningKeyPair() == null) { + keySet.setKeys(new JWK[] {}); + } else { + keySet.setKeys(new JWK[] { JWKBuilder.create().rs256(clientData.getSigningKeyPair().getPublic()) }); + } + + return keySet; + } + + + @GET + @Path("/set-oidc-request") + @Produces(org.keycloak.utils.MediaType.APPLICATION_JWT) + @NoCache + public void setOIDCRequest(@QueryParam("realmName") String realmName, @QueryParam("clientId") String clientId, + @QueryParam("redirectUri") String redirectUri, @QueryParam("maxAge") String maxAge, + @QueryParam("jwaAlgorithm") String jwaAlgorithm) { + Map oidcRequest = new HashMap<>(); + oidcRequest.put(OIDCLoginProtocol.CLIENT_ID_PARAM, clientId); + oidcRequest.put(OIDCLoginProtocol.RESPONSE_TYPE_PARAM, OAuth2Constants.CODE); + oidcRequest.put(OIDCLoginProtocol.REDIRECT_URI_PARAM, redirectUri); + if (maxAge != null) { + oidcRequest.put(OIDCLoginProtocol.MAX_AGE_PARAM, Integer.parseInt(maxAge)); + } + + Algorithm alg = Enum.valueOf(Algorithm.class, jwaAlgorithm); + if (alg == Algorithm.none) { + clientData.setOidcRequest(new JWSBuilder().jsonContent(oidcRequest).none()); + } else if (alg == Algorithm.RS256) { + if (clientData.getSigningKeyPair() == null) { + throw new BadRequestException("Requested RS256, but signing key not set"); + } + + PrivateKey privateKey = clientData.getSigningKeyPair().getPrivate(); + clientData.setOidcRequest(new JWSBuilder().jsonContent(oidcRequest).rsa256(privateKey)); + } else { + throw new BadRequestException("Unknown argument: " + jwaAlgorithm); + } + } + + + @GET + @Path("/get-oidc-request") + @Produces(org.keycloak.utils.MediaType.APPLICATION_JWT) + @NoCache + public String getOIDCRequest() { + return clientData.getOidcRequest(); + } + + @GET + @Path("/set-sector-identifier-redirect-uris") + @Produces(MediaType.APPLICATION_JSON) + public void setSectorIdentifierRedirectUris(@QueryParam("redirectUris") List redirectUris) { + clientData.setSectorIdentifierRedirectUris(new ArrayList<>()); + clientData.getSectorIdentifierRedirectUris().addAll(redirectUris); + } + + @GET + @Path("/get-sector-identifier-redirect-uris") + @Produces(MediaType.APPLICATION_JSON) + public List getSectorIdentifierRedirectUris() { + return clientData.getSectorIdentifierRedirectUris(); + } +} diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/META-INF/keycloak-themes.json b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/META-INF/keycloak-themes.json new file mode 100644 index 0000000000..03978db442 --- /dev/null +++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/META-INF/keycloak-themes.json @@ -0,0 +1,6 @@ +{ + "themes": [{ + "name" : "address", + "types": [ "admin", "account", "login" ] + }] +} \ No newline at end of file diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/theme/address/account/account.ftl b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/theme/address/account/account.ftl new file mode 100755 index 0000000000..d2a6af16e0 --- /dev/null +++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/theme/address/account/account.ftl @@ -0,0 +1,114 @@ +<#import "template.ftl" as layout> +<@layout.mainLayout active='account' bodyClass='user'; section> + +
+
+

${msg("editAccountHtmlTtile")}

+
+
+ * ${msg("requiredFields")} +
+
+ +
+ + + +
+
+ <#if realm.editUsernameAllowed>* +
+ +
+ disabled="disabled" value="${(account.username!'')?html}"/> +
+
+ +
+
+ * +
+ +
+ +
+
+ +
+
+ * +
+ +
+ +
+
+ +
+
+ * +
+ +
+ +
+
+ +
+
+ +
+ +
+ +
+
+
+
+ +
+ +
+ +
+
+
+
+ +
+ +
+ +
+
+
+
+ +
+ +
+ +
+
+
+
+ +
+ +
+ +
+
+ + + + + \ No newline at end of file diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/theme/address/account/theme.properties b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/theme/address/account/theme.properties new file mode 100644 index 0000000000..3e50437b9a --- /dev/null +++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/theme/address/account/theme.properties @@ -0,0 +1,18 @@ +# +# Copyright 2016 Red Hat, Inc. and/or its affiliates +# and other contributors as indicated by the @author tags. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +parent=keycloak \ No newline at end of file diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/theme/address/admin/resources/partials/user-attributes.html b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/theme/address/admin/resources/partials/user-attributes.html new file mode 100755 index 0000000000..af512de26e --- /dev/null +++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/theme/address/admin/resources/partials/user-attributes.html @@ -0,0 +1,72 @@ + + +
+ + + + +
+
+ +
+ +
+ Street address. +
+
+ +
+ +
+ City or locality. +
+
+ +
+ +
+ State, province, prefecture, or region. +
+
+ +
+ +
+ Zip code or postal code. +
+
+ +
+ +
+ Country name. +
+ +
+
+ + +
+
+
+
+ + diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/theme/address/admin/theme.properties b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/theme/address/admin/theme.properties new file mode 100644 index 0000000000..3e50437b9a --- /dev/null +++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/theme/address/admin/theme.properties @@ -0,0 +1,18 @@ +# +# Copyright 2016 Red Hat, Inc. and/or its affiliates +# and other contributors as indicated by the @author tags. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +parent=keycloak \ No newline at end of file diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/theme/address/login/login-update-profile.ftl b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/theme/address/login/login-update-profile.ftl new file mode 100755 index 0000000000..e02a340405 --- /dev/null +++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/theme/address/login/login-update-profile.ftl @@ -0,0 +1,95 @@ +<#import "template.ftl" as layout> +<@layout.registrationLayout; section> + <#if section = "title"> + ${msg("loginProfileTitle")} + <#elseif section = "header"> + ${msg("loginProfileTitle")} + <#elseif section = "form"> +
+
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+ +
+ +
+
+
+
+ +
+ +
+ +
+
+
+
+ +
+ +
+ +
+
+
+
+ +
+ +
+ +
+
+
+
+ +
+ +
+ +
+
+ + +
+
+
+
+
+ +
+ +
+
+
+ + \ No newline at end of file diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/theme/address/login/register.ftl b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/theme/address/login/register.ftl new file mode 100755 index 0000000000..3247305cf9 --- /dev/null +++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/theme/address/login/register.ftl @@ -0,0 +1,131 @@ +<#import "template.ftl" as layout> +<@layout.registrationLayout; section> + <#if section = "title"> + ${msg("registerWithTitle",(realm.name!''))} + <#elseif section = "header"> + ${msg("registerWithTitleHtml",(realm.name!''))} + <#elseif section = "form"> +
+ <#if !realm.registrationEmailAsUsername> +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ + <#if passwordRequired> +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+ +
+ +
+
+
+
+ +
+ +
+ +
+
+
+
+ +
+ +
+ +
+
+
+
+ +
+ +
+ +
+
+
+
+ +
+ +
+ +
+
+ <#if recaptchaRequired??> +
+
+
+
+
+ + +
+ + +
+ +
+
+
+ + \ No newline at end of file diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/theme/address/login/theme.properties b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/theme/address/login/theme.properties new file mode 100644 index 0000000000..3e50437b9a --- /dev/null +++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/theme/address/login/theme.properties @@ -0,0 +1,18 @@ +# +# Copyright 2016 Red Hat, Inc. and/or its affiliates +# and other contributors as indicated by the @author tags. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +parent=keycloak \ No newline at end of file diff --git a/testsuite/integration-arquillian/servers/migration/wildfly_kc15/assembly.xml b/testsuite/integration-arquillian/servers/migration/assembly.xml similarity index 84% rename from testsuite/integration-arquillian/servers/migration/wildfly_kc15/assembly.xml rename to testsuite/integration-arquillian/servers/migration/assembly.xml index b7330e32b2..e1e853da91 100644 --- a/testsuite/integration-arquillian/servers/migration/wildfly_kc15/assembly.xml +++ b/testsuite/integration-arquillian/servers/migration/assembly.xml @@ -17,7 +17,7 @@ - auth-server-jboss-kc15 + auth-server-jboss-${migrated.auth.server.version} zip @@ -28,14 +28,14 @@ ${keycloak.server.home} - keycloak-1.5.1.Final + keycloak-${migrated.auth.server.version} **/*.sh ${keycloak.server.home} - keycloak-1.5.1.Final + keycloak-${migrated.auth.server.version} **/*.sh diff --git a/testsuite/integration-arquillian/servers/migration/pom.xml b/testsuite/integration-arquillian/servers/migration/pom.xml index 6f6301ae0b..b7a5ff917e 100644 --- a/testsuite/integration-arquillian/servers/migration/pom.xml +++ b/testsuite/integration-arquillian/servers/migration/pom.xml @@ -1,20 +1,20 @@ +~ Copyright 2016 Red Hat, Inc. and/or its affiliates +~ and other contributors as indicated by the @author tags. +~ +~ Licensed under the Apache License, Version 2.0 (the "License"); +~ you may not use this file except in compliance with the License. +~ You may obtain a copy of the License at +~ +~ http://www.apache.org/licenses/LICENSE-2.0 +~ +~ Unless required by applicable law or agreed to in writing, software +~ distributed under the License is distributed on an "AS IS" BASIS, +~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +~ See the License for the specific language governing permissions and +~ limitations under the License. +--> @@ -25,231 +25,195 @@ 4.0.0 - integration-arquillian-migration-servers + integration-arquillian-migration-server pom - Migration Servers + Migration Server + + + ${project.build.directory}/unpacked/keycloak-${migrated.auth.server.version} + ${keycloak.server.home}/modules/system/layers/base/com/${jdbc.mvn.artifactId}/main + - - - migration-kc16 - - wildfly_kc16 - - - - migration-kc15 - - wildfly_kc15 - - - - migration-kc14 - - wildfly_kc14 - - - - migration-kc13 - - wildfly_kc13 - - - - migration-kc12 - - wildfly_kc12 - - - - org.apache.maven.plugins - maven-enforcer-plugin + maven-deploy-plugin true + + org.apache.maven.plugins + maven-enforcer-plugin + + + enforce-properties + + enforce + + + + + migrated.auth.server.version + + + jdbc.mvn.groupId + + + jdbc.mvn.artifactId + + + jdbc.mvn.version + + + keycloak.connectionsJpa.url + + + keycloak.connectionsJpa.user + + + keycloak.connectionsJpa.password + + + + + + + + org.apache.maven.plugins + maven-dependency-plugin + + + unpack-server + generate-resources + + unpack + + + + + org.keycloak + keycloak-server-dist + ${migrated.auth.server.version} + zip + ${project.build.directory}/unpacked + + + + + + jdbc-driver + process-resources + + copy + + + + + ${jdbc.mvn.groupId} + ${jdbc.mvn.artifactId} + ${jdbc.mvn.version} + jar + + + ${jdbc.mvn.driver.deployment.dir} + true + + + + + + org.codehaus.mojo + xml-maven-plugin + + + configure-wildfly-datasource + process-resources + + transform + + + + + + ${keycloak.server.home}/modules/system/layers/base/com/h2database/h2/main + src/main/xslt/module.xsl + + module.xml + + ${jdbc.mvn.driver.deployment.dir} + + + database + ${jdbc.mvn.artifactId} + + + version + ${jdbc.mvn.version} + + + + + + ${keycloak.server.home}/standalone/configuration + src/main/xslt/datasource.xsl + + standalone.xml + + ${keycloak.server.home}/standalone/configuration + + + jdbc.url + ${keycloak.connectionsJpa.url} + + + driver + ${jdbc.mvn.artifactId} + + + username + ${keycloak.connectionsJpa.user} + + + password + ${keycloak.connectionsJpa.password} + + + + + + ${keycloak.server.home}/standalone/configuration + src/main/xslt/add-dialect-logger.xsl + + standalone.xml + + ${keycloak.server.home}/standalone/configuration + + + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + create-zip + package + + single + + + + assembly.xml + + false + + + + - - - - org.apache.maven.plugins - maven-deploy-plugin - - true - - - - org.apache.maven.plugins - maven-enforcer-plugin - - - enforce-properties - - enforce - - - - - jdbc.mvn.groupId - - - jdbc.mvn.artifactId - - - jdbc.mvn.version - - - keycloak.connectionsJpa.url - - - keycloak.connectionsJpa.user - - - keycloak.connectionsJpa.password - - - - - - - - org.apache.maven.plugins - maven-dependency-plugin - - - unpack-server - generate-resources - - unpack - - - - - org.keycloak - keycloak-server-dist - ${server.version} - zip - ${project.build.directory}/unpacked - - - - - - jdbc-driver - process-resources - - copy - - - - - ${jdbc.mvn.groupId} - ${jdbc.mvn.artifactId} - ${jdbc.mvn.version} - jar - - - ${jdbc.mvn.driver.deployment.dir} - true - - - - - - org.codehaus.mojo - xml-maven-plugin - - - configure-wildfly-datasource - process-resources - - transform - - - - - - ${keycloak.server.home}/modules/system/layers/base/com/h2database/h2/main - src/main/xslt/module.xsl - - module.xml - - ${jdbc.mvn.driver.deployment.dir} - - - database - ${jdbc.mvn.artifactId} - - - version - ${jdbc.mvn.version} - - - - - - ${keycloak.server.home}/standalone/configuration - src/main/xslt/datasource.xsl - - standalone.xml - - ${keycloak.server.home}/standalone/configuration - - - jdbc.url - ${keycloak.connectionsJpa.url} - - - driver - ${jdbc.mvn.artifactId} - - - username - ${keycloak.connectionsJpa.user} - - - password - ${keycloak.connectionsJpa.password} - - - - - - ${keycloak.server.home}/standalone/configuration - src/main/xslt/add-dialect-logger.xsl - - standalone.xml - - ${keycloak.server.home}/standalone/configuration - - - - - - - - org.apache.maven.plugins - maven-assembly-plugin - - - create-zip - package - - single - - - - assembly.xml - - false - - - - - - diff --git a/testsuite/integration-arquillian/servers/migration/wildfly_kc12/src/main/xslt/add-dialect-logger.xsl b/testsuite/integration-arquillian/servers/migration/src/main/xslt/add-dialect-logger.xsl similarity index 100% rename from testsuite/integration-arquillian/servers/migration/wildfly_kc12/src/main/xslt/add-dialect-logger.xsl rename to testsuite/integration-arquillian/servers/migration/src/main/xslt/add-dialect-logger.xsl diff --git a/testsuite/integration-arquillian/servers/migration/wildfly_kc12/src/main/xslt/datasource.xsl b/testsuite/integration-arquillian/servers/migration/src/main/xslt/datasource.xsl similarity index 93% rename from testsuite/integration-arquillian/servers/migration/wildfly_kc12/src/main/xslt/datasource.xsl rename to testsuite/integration-arquillian/servers/migration/src/main/xslt/datasource.xsl index 1fc4d87e7f..b68a3b1d41 100644 --- a/testsuite/integration-arquillian/servers/migration/wildfly_kc12/src/main/xslt/datasource.xsl +++ b/testsuite/integration-arquillian/servers/migration/src/main/xslt/datasource.xsl @@ -17,17 +17,12 @@ + exclude-result-prefixes="xalan"> - diff --git a/testsuite/integration-arquillian/servers/migration/wildfly_kc14/src/main/xslt/module.xsl b/testsuite/integration-arquillian/servers/migration/src/main/xslt/module.xsl similarity index 94% rename from testsuite/integration-arquillian/servers/migration/wildfly_kc14/src/main/xslt/module.xsl rename to testsuite/integration-arquillian/servers/migration/src/main/xslt/module.xsl index f57019bdcd..766dd82013 100644 --- a/testsuite/integration-arquillian/servers/migration/wildfly_kc14/src/main/xslt/module.xsl +++ b/testsuite/integration-arquillian/servers/migration/src/main/xslt/module.xsl @@ -17,9 +17,8 @@ + exclude-result-prefixes="xalan"> diff --git a/testsuite/integration-arquillian/servers/migration/wildfly_kc12/assembly.xml b/testsuite/integration-arquillian/servers/migration/wildfly_kc12/assembly.xml deleted file mode 100644 index cc9969730e..0000000000 --- a/testsuite/integration-arquillian/servers/migration/wildfly_kc12/assembly.xml +++ /dev/null @@ -1,46 +0,0 @@ - - - - - auth-server-jboss-kc14 - - - zip - - - false - - - - ${keycloak.server.home} - keycloak-1.2.0.Final - - **/*.sh - - - - ${keycloak.server.home} - keycloak-1.2.0.Final - - **/*.sh - - 0755 - - - - diff --git a/testsuite/integration-arquillian/servers/migration/wildfly_kc12/pom.xml b/testsuite/integration-arquillian/servers/migration/wildfly_kc12/pom.xml deleted file mode 100644 index 00407cadad..0000000000 --- a/testsuite/integration-arquillian/servers/migration/wildfly_kc12/pom.xml +++ /dev/null @@ -1,61 +0,0 @@ - - - - - - org.keycloak.testsuite - integration-arquillian-migration-servers - 2.2.0-SNAPSHOT - - 4.0.0 - - integration-arquillian-server-wildfly-kc12 - pom - Keycloak 1.2.0.Final on Wildfly - - - 1.2.0.Final - ${project.build.directory}/unpacked/keycloak-${server.version} - ${keycloak.server.home}/modules/system/layers/base/com/${jdbc.mvn.artifactId}/main - - - - - - org.apache.maven.plugins - maven-enforcer-plugin - - false - - - - org.apache.maven.plugins - maven-dependency-plugin - - - org.codehaus.mojo - xml-maven-plugin - - - org.apache.maven.plugins - maven-assembly-plugin - - - - diff --git a/testsuite/integration-arquillian/servers/migration/wildfly_kc12/src/main/xslt/module.xsl b/testsuite/integration-arquillian/servers/migration/wildfly_kc12/src/main/xslt/module.xsl deleted file mode 100644 index f57019bdcd..0000000000 --- a/testsuite/integration-arquillian/servers/migration/wildfly_kc12/src/main/xslt/module.xsl +++ /dev/null @@ -1,50 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/testsuite/integration-arquillian/servers/migration/wildfly_kc13/assembly.xml b/testsuite/integration-arquillian/servers/migration/wildfly_kc13/assembly.xml deleted file mode 100644 index cd48ba2349..0000000000 --- a/testsuite/integration-arquillian/servers/migration/wildfly_kc13/assembly.xml +++ /dev/null @@ -1,46 +0,0 @@ - - - - - auth-server-jboss-kc14 - - - zip - - - false - - - - ${keycloak.server.home} - keycloak-1.3.1.Final - - **/*.sh - - - - ${keycloak.server.home} - keycloak-1.3.1.Final - - **/*.sh - - 0755 - - - - diff --git a/testsuite/integration-arquillian/servers/migration/wildfly_kc13/pom.xml b/testsuite/integration-arquillian/servers/migration/wildfly_kc13/pom.xml deleted file mode 100644 index 85124514d5..0000000000 --- a/testsuite/integration-arquillian/servers/migration/wildfly_kc13/pom.xml +++ /dev/null @@ -1,62 +0,0 @@ - - - - - - org.keycloak.testsuite - integration-arquillian-migration-servers - 2.2.0-SNAPSHOT - - 4.0.0 - - integration-arquillian-server-wildfly-kc13 - pom - Keycloak 1.3.1.Final on Wildfly - - - 1.3.1.Final - ${project.build.directory}/unpacked/keycloak-${server.version} - ${keycloak.server.home}/modules/system/layers/base/com/${jdbc.mvn.artifactId}/main - - - - - - org.apache.maven.plugins - maven-enforcer-plugin - - false - - - - org.apache.maven.plugins - maven-dependency-plugin - - - org.codehaus.mojo - xml-maven-plugin - - - org.apache.maven.plugins - maven-assembly-plugin - - - - - diff --git a/testsuite/integration-arquillian/servers/migration/wildfly_kc13/src/main/xslt/add-dialect-logger.xsl b/testsuite/integration-arquillian/servers/migration/wildfly_kc13/src/main/xslt/add-dialect-logger.xsl deleted file mode 100644 index 6b9e94a1ce..0000000000 --- a/testsuite/integration-arquillian/servers/migration/wildfly_kc13/src/main/xslt/add-dialect-logger.xsl +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/testsuite/integration-arquillian/servers/migration/wildfly_kc13/src/main/xslt/datasource.xsl b/testsuite/integration-arquillian/servers/migration/wildfly_kc13/src/main/xslt/datasource.xsl deleted file mode 100644 index 1fc4d87e7f..0000000000 --- a/testsuite/integration-arquillian/servers/migration/wildfly_kc13/src/main/xslt/datasource.xsl +++ /dev/null @@ -1,111 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/testsuite/integration-arquillian/servers/migration/wildfly_kc13/src/main/xslt/module.xsl b/testsuite/integration-arquillian/servers/migration/wildfly_kc13/src/main/xslt/module.xsl deleted file mode 100644 index f57019bdcd..0000000000 --- a/testsuite/integration-arquillian/servers/migration/wildfly_kc13/src/main/xslt/module.xsl +++ /dev/null @@ -1,50 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/testsuite/integration-arquillian/servers/migration/wildfly_kc14/assembly.xml b/testsuite/integration-arquillian/servers/migration/wildfly_kc14/assembly.xml deleted file mode 100644 index deac59de4f..0000000000 --- a/testsuite/integration-arquillian/servers/migration/wildfly_kc14/assembly.xml +++ /dev/null @@ -1,46 +0,0 @@ - - - - - auth-server-jboss-kc14 - - - zip - - - false - - - - ${keycloak.server.home} - keycloak-1.4.0.Final - - **/*.sh - - - - ${keycloak.server.home} - keycloak-1.4.0.Final - - **/*.sh - - 0755 - - - - diff --git a/testsuite/integration-arquillian/servers/migration/wildfly_kc14/pom.xml b/testsuite/integration-arquillian/servers/migration/wildfly_kc14/pom.xml deleted file mode 100644 index baa4b88ea3..0000000000 --- a/testsuite/integration-arquillian/servers/migration/wildfly_kc14/pom.xml +++ /dev/null @@ -1,61 +0,0 @@ - - - - - - org.keycloak.testsuite - integration-arquillian-migration-servers - 2.2.0-SNAPSHOT - - 4.0.0 - - integration-arquillian-server-wildfly-kc14 - pom - Keycloak 1.4.0.Final on Wildfly - - - 1.4.0.Final - ${project.build.directory}/unpacked/keycloak-${server.version} - ${keycloak.server.home}/modules/system/layers/base/com/${jdbc.mvn.artifactId}/main - - - - - - org.apache.maven.plugins - maven-enforcer-plugin - - false - - - - org.apache.maven.plugins - maven-dependency-plugin - - - org.codehaus.mojo - xml-maven-plugin - - - org.apache.maven.plugins - maven-assembly-plugin - - - - diff --git a/testsuite/integration-arquillian/servers/migration/wildfly_kc14/src/main/xslt/add-dialect-logger.xsl b/testsuite/integration-arquillian/servers/migration/wildfly_kc14/src/main/xslt/add-dialect-logger.xsl deleted file mode 100644 index 6b9e94a1ce..0000000000 --- a/testsuite/integration-arquillian/servers/migration/wildfly_kc14/src/main/xslt/add-dialect-logger.xsl +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/testsuite/integration-arquillian/servers/migration/wildfly_kc14/src/main/xslt/datasource.xsl b/testsuite/integration-arquillian/servers/migration/wildfly_kc14/src/main/xslt/datasource.xsl deleted file mode 100644 index 1fc4d87e7f..0000000000 --- a/testsuite/integration-arquillian/servers/migration/wildfly_kc14/src/main/xslt/datasource.xsl +++ /dev/null @@ -1,111 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/testsuite/integration-arquillian/servers/migration/wildfly_kc15/pom.xml b/testsuite/integration-arquillian/servers/migration/wildfly_kc15/pom.xml deleted file mode 100644 index 5928cd41c6..0000000000 --- a/testsuite/integration-arquillian/servers/migration/wildfly_kc15/pom.xml +++ /dev/null @@ -1,61 +0,0 @@ - - - - - - org.keycloak.testsuite - integration-arquillian-migration-servers - 2.2.0-SNAPSHOT - - 4.0.0 - - integration-arquillian-server-wildfly-kc15 - pom - Keycloak 1.5.1.Final on Wildfly - - - 1.5.1.Final - ${project.build.directory}/unpacked/keycloak-${server.version} - ${keycloak.server.home}/modules/system/layers/base/com/${jdbc.mvn.artifactId}/main - - - - - - org.apache.maven.plugins - maven-enforcer-plugin - - false - - - - org.apache.maven.plugins - maven-dependency-plugin - - - org.codehaus.mojo - xml-maven-plugin - - - org.apache.maven.plugins - maven-assembly-plugin - - - - diff --git a/testsuite/integration-arquillian/servers/migration/wildfly_kc15/src/main/xslt/add-dialect-logger.xsl b/testsuite/integration-arquillian/servers/migration/wildfly_kc15/src/main/xslt/add-dialect-logger.xsl deleted file mode 100644 index 6b9e94a1ce..0000000000 --- a/testsuite/integration-arquillian/servers/migration/wildfly_kc15/src/main/xslt/add-dialect-logger.xsl +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/testsuite/integration-arquillian/servers/migration/wildfly_kc15/src/main/xslt/datasource.xsl b/testsuite/integration-arquillian/servers/migration/wildfly_kc15/src/main/xslt/datasource.xsl deleted file mode 100644 index 1fc4d87e7f..0000000000 --- a/testsuite/integration-arquillian/servers/migration/wildfly_kc15/src/main/xslt/datasource.xsl +++ /dev/null @@ -1,111 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/testsuite/integration-arquillian/servers/migration/wildfly_kc15/src/main/xslt/module.xsl b/testsuite/integration-arquillian/servers/migration/wildfly_kc15/src/main/xslt/module.xsl deleted file mode 100644 index f57019bdcd..0000000000 --- a/testsuite/integration-arquillian/servers/migration/wildfly_kc15/src/main/xslt/module.xsl +++ /dev/null @@ -1,50 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/testsuite/integration-arquillian/servers/migration/wildfly_kc16/assembly.xml b/testsuite/integration-arquillian/servers/migration/wildfly_kc16/assembly.xml deleted file mode 100644 index 2e08956694..0000000000 --- a/testsuite/integration-arquillian/servers/migration/wildfly_kc16/assembly.xml +++ /dev/null @@ -1,46 +0,0 @@ - - - - - auth-server-jboss-kc16 - - - zip - - - false - - - - ${keycloak.server.home} - keycloak-1.6.1.Final - - **/*.sh - - - - ${keycloak.server.home} - keycloak-1.6.1.Final - - **/*.sh - - 0755 - - - - diff --git a/testsuite/integration-arquillian/servers/migration/wildfly_kc16/pom.xml b/testsuite/integration-arquillian/servers/migration/wildfly_kc16/pom.xml deleted file mode 100644 index a919f053ee..0000000000 --- a/testsuite/integration-arquillian/servers/migration/wildfly_kc16/pom.xml +++ /dev/null @@ -1,61 +0,0 @@ - - - - - - org.keycloak.testsuite - integration-arquillian-migration-servers - 2.2.0-SNAPSHOT - - 4.0.0 - - integration-arquillian-server-wildfly-kc16 - pom - Keycloak 1.6.1.Final on Wildfly - - - 1.6.1.Final - ${project.build.directory}/unpacked/keycloak-${server.version} - ${keycloak.server.home}/modules/system/layers/base/com/${jdbc.mvn.artifactId}/main - - - - - - org.apache.maven.plugins - maven-enforcer-plugin - - false - - - - org.apache.maven.plugins - maven-dependency-plugin - - - org.codehaus.mojo - xml-maven-plugin - - - org.apache.maven.plugins - maven-assembly-plugin - - - - diff --git a/testsuite/integration-arquillian/servers/migration/wildfly_kc16/src/main/xslt/add-dialect-logger.xsl b/testsuite/integration-arquillian/servers/migration/wildfly_kc16/src/main/xslt/add-dialect-logger.xsl deleted file mode 100644 index 6b9e94a1ce..0000000000 --- a/testsuite/integration-arquillian/servers/migration/wildfly_kc16/src/main/xslt/add-dialect-logger.xsl +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/testsuite/integration-arquillian/servers/migration/wildfly_kc16/src/main/xslt/datasource.xsl b/testsuite/integration-arquillian/servers/migration/wildfly_kc16/src/main/xslt/datasource.xsl deleted file mode 100644 index 1fc4d87e7f..0000000000 --- a/testsuite/integration-arquillian/servers/migration/wildfly_kc16/src/main/xslt/datasource.xsl +++ /dev/null @@ -1,111 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/testsuite/integration-arquillian/servers/migration/wildfly_kc16/src/main/xslt/module.xsl b/testsuite/integration-arquillian/servers/migration/wildfly_kc16/src/main/xslt/module.xsl deleted file mode 100644 index f57019bdcd..0000000000 --- a/testsuite/integration-arquillian/servers/migration/wildfly_kc16/src/main/xslt/module.xsl +++ /dev/null @@ -1,50 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api-authz-service.json b/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api-authz-service.json index 6547d2fcc4..1ce85dd9dc 100644 --- a/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api-authz-service.json +++ b/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api-authz-service.json @@ -50,7 +50,7 @@ "mavenArtifactVersion": "2.1.0-SNAPSHOT", "mavenArtifactId": "photoz-authz-policy", "sessionName": "MainOwnerSession", - "mavenArtifactGroupId": "org.keycloak", + "mavenArtifactGroupId": "org.keycloak.testsuite", "moduleName": "PhotozAuthzOwnerPolicy", "applyPolicies": "[]", "scannerPeriod": "1", diff --git a/testsuite/integration-arquillian/test-utils/pom.xml b/testsuite/integration-arquillian/test-utils/pom.xml new file mode 100644 index 0000000000..c03c56b334 --- /dev/null +++ b/testsuite/integration-arquillian/test-utils/pom.xml @@ -0,0 +1,34 @@ + + + + integration-arquillian + org.keycloak.testsuite + 2.2.0-SNAPSHOT + + 4.0.0 + + integration-arquillian-test-utils + jar + + Test utils + + + + junit + junit + compile + + + org.jboss.logging + jboss-logging + + + commons-configuration + commons-configuration + 1.10 + + + + \ No newline at end of file diff --git a/testsuite/integration-arquillian/test-utils/src/main/java/org/keycloak/testsuite/util/junit/AggregateResultsReporter.java b/testsuite/integration-arquillian/test-utils/src/main/java/org/keycloak/testsuite/util/junit/AggregateResultsReporter.java new file mode 100644 index 0000000000..2b1bf6ce04 --- /dev/null +++ b/testsuite/integration-arquillian/test-utils/src/main/java/org/keycloak/testsuite/util/junit/AggregateResultsReporter.java @@ -0,0 +1,280 @@ +package org.keycloak.testsuite.util.junit; + +import org.apache.commons.configuration.PropertiesConfiguration; + +import org.jboss.logging.Logger; + +import org.junit.Ignore; +import org.junit.runner.Description; +import org.junit.runner.Result; +import org.junit.runner.notification.Failure; +import org.junit.runner.notification.RunListener; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerConfigurationException; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.nio.charset.Charset; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReference; + +/** + * Aggregates jUnit test results into a single report - XML file. + */ +public class AggregateResultsReporter extends RunListener { + + private static final Logger LOGGER = Logger.getLogger(AggregateResultsReporter.class); + + private final Document xml; + private final File reportFile; + private final boolean working; + + private final AtomicInteger tests = new AtomicInteger(0); + private final AtomicInteger errors = new AtomicInteger(0); + private final AtomicInteger failures = new AtomicInteger(0); + private final AtomicInteger ignored = new AtomicInteger(0); + private final AtomicLong suiteStartTime = new AtomicLong(0L); + + private final AtomicReference testsuite = new AtomicReference(); + + private final Map testTimes = new HashMap(); + + public AggregateResultsReporter() { + boolean working = true; + Document xml = null; + try { + xml = createEmptyDocument(); + } catch (ParserConfigurationException ex) { + LOGGER.error("Failed to create XML DOM - reporting will not be done", ex); + working = false; + } + + File reportFile = null; + try { + reportFile = createReportFile(); + } catch (Exception ex) { + LOGGER.error("Failed to create log file - reporting will not be done", ex); + working = false; + } + + this.working = working; + this.xml = xml; + this.reportFile = reportFile; + } + + private Document createEmptyDocument() throws ParserConfigurationException { + DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); + return builder.newDocument(); + } + + private File createReportFile() throws Exception { + String logDirPath = null; + + try { + PropertiesConfiguration config = new PropertiesConfiguration(System.getProperty("testsuite.constants")); + config.setThrowExceptionOnMissing(true); + logDirPath = config.getString("log-dir"); + } catch (Exception e) { + logDirPath = System.getProperty("project.build.directory"); + if (logDirPath == null) { + throw new RuntimeException("Could not determine the path to the log directory."); + } + logDirPath += File.separator + "surefire-reports"; + } + + final File logDir = new File(logDirPath); + logDir.mkdirs(); + + final File reportFile = new File(logDir, "junit-single-report.xml").getAbsoluteFile(); + reportFile.createNewFile(); + + return reportFile; + } + + @Override + public void testRunStarted(Description description) throws Exception { + if (working) { + suiteStartTime.set(System.currentTimeMillis()); + + Element testsuite = xml.createElement("testsuite"); + + if (description.getChildren().size() == 1) { + testsuite.setAttribute("name", safeString(description.getChildren().get(0).getDisplayName())); + } + + xml.appendChild(testsuite); + this.testsuite.set(testsuite); + writeXml(); + } + } + + @Override + public void testStarted(Description description) throws Exception { + if (working) { + testTimes.put(description.getDisplayName(), System.currentTimeMillis()); + } + } + + @Override + public void testFinished(Description description) throws Exception { + if (working) { + if (testTimes.containsKey(description.getDisplayName())) { + testsuite.get().appendChild(createTestCase(description)); + writeXml(); + } + } + } + + @Override + public void testAssumptionFailure(Failure failure) { + if (working) { + ignored.incrementAndGet(); + + Element testcase = createTestCase(failure.getDescription()); + Element skipped = xml.createElement("skipped"); + skipped.setAttribute("message", safeString(failure.getMessage())); + + testcase.appendChild(skipped); + + testsuite.get().appendChild(testcase); + writeXml(); + } + } + + @Override + public void testFailure(Failure failure) throws Exception { + if (working) { + if (failure.getDescription().getMethodName() == null) { + // before class failed + for (Description child : failure.getDescription().getChildren()) { + // mark all methods failed + testFailure(new Failure(child, failure.getException())); + } + } else { + // normal failure + Element testcase = createTestCase(failure.getDescription()); + + Element element; + if (failure.getException() instanceof AssertionError) { + failures.incrementAndGet(); + element = xml.createElement("failure"); + } else { + errors.incrementAndGet(); + element = xml.createElement("error"); + } + + testcase.appendChild(element); + + element.setAttribute("type", safeString(failure.getException().getClass().getName())); + element.setAttribute("message", safeString(failure.getMessage())); + element.appendChild(xml.createCDATASection(safeString(failure.getTrace()))); + + testsuite.get().appendChild(testcase); + writeXml(); + } + } + } + + @Override + public void testIgnored(Description description) throws Exception { + if (working) { + ignored.incrementAndGet(); + + Element testcase = createTestCase(description); + + Element skipped = xml.createElement("skipped"); + skipped.setAttribute("message", safeString(description.getAnnotation(Ignore.class).value())); + + testcase.appendChild(skipped); + + testsuite.get().appendChild(testcase); + writeXml(); + } + } + + @Override + public void testRunFinished(Result result) throws Exception { + if (working) { + writeXml(); + } + } + + private void writeXml() { + Element testsuite = this.testsuite.get(); + + testsuite.setAttribute("tests", Integer.toString(tests.get())); + testsuite.setAttribute("errors", Integer.toString(errors.get())); + testsuite.setAttribute("skipped", Integer.toString(ignored.get())); + testsuite.setAttribute("failures", Integer.toString(failures.get())); + testsuite.setAttribute("time", computeTestTime(suiteStartTime.get())); + + try { + Writer writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(reportFile, false), Charset.forName("UTF-8"))); + try { + Transformer t = TransformerFactory.newInstance().newTransformer(); + t.setOutputProperty(OutputKeys.INDENT, "yes"); + t.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4"); + t.transform(new DOMSource(xml), new StreamResult(writer)); + } catch (TransformerConfigurationException ex) { + LOGGER.error("Misconfigured transformer", ex); + } catch (TransformerException ex) { + LOGGER.error("Unable to save XML file", ex); + } finally { + writer.close(); + } + } catch (IOException ex) { + LOGGER.warn("Unable to open report file", ex); + } + } + + private String computeTestTime(Long startTime) { + if (startTime == null) { + return "0"; + } else { + long amount = System.currentTimeMillis() - startTime; + return String.format("%.3f", amount / 1000F); + } + } + + private Element createTestCase(Description description) { + tests.incrementAndGet(); + + Element testcase = xml.createElement("testcase"); + + testcase.setAttribute("name", safeString(description.getMethodName())); + testcase.setAttribute("classname", safeString(description.getClassName())); + testcase.setAttribute("time", computeTestTime(testTimes.remove(description.getDisplayName()))); + + return testcase; + } + + private String safeString(String input) { + if (input == null) { + return "null"; + } + + return input + // first remove color coding (all of it) + .replaceAll("\u001b\\[\\d+m", "") + // then remove control characters that are not whitespaces + .replaceAll("[\\p{Cntrl}&&[^\\p{Space}]]", ""); + } +} diff --git a/testsuite/integration-arquillian/tests/base/pom.xml b/testsuite/integration-arquillian/tests/base/pom.xml index ac85ebd650..15594dae28 100644 --- a/testsuite/integration-arquillian/tests/base/pom.xml +++ b/testsuite/integration-arquillian/tests/base/pom.xml @@ -36,8 +36,6 @@ - - - - - **/migration/**/*Test.java **/cluster/**/*Test.java @@ -102,7 +100,6 @@ ${exclude.console} ${exclude.account} ${exclude.client} - ${exclude.migration} ${exclude.cluster} @@ -137,6 +134,7 @@ copy-resources + ${skip.add.user.json} ${auth.server.config.dir} diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/ProfileAssume.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/ProfileAssume.java new file mode 100644 index 0000000000..7fbf59b068 --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/ProfileAssume.java @@ -0,0 +1,36 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.keycloak.testsuite; + +import org.junit.Assume; +import org.keycloak.common.Profile; + +/** + * @author Stian Thorgersen + */ +public class ProfileAssume { + + public static void assumePreview() { + Assume.assumeTrue("Ignoring test as community/preview profile is not enabled", Profile.isPreviewEnabled()); + } + + public static void assumePreviewDisabled() { + Assume.assumeFalse("Ignoring test as community/preview profile is enabled", Profile.isPreviewEnabled()); + } + +} diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/filter/AdapterActionsFilter.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/filter/AdapterActionsFilter.java index c282dff966..aad5bb889d 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/filter/AdapterActionsFilter.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/filter/AdapterActionsFilter.java @@ -27,8 +27,13 @@ import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import org.jboss.logging.Logger; +import org.keycloak.adapters.AdapterDeploymentContext; +import org.keycloak.adapters.KeycloakDeployment; +import org.keycloak.adapters.rotation.JWKPublicKeyLocator; import org.keycloak.common.util.Time; /** @@ -38,6 +43,11 @@ import org.keycloak.common.util.Time; */ public class AdapterActionsFilter implements Filter { + public static final String TIME_OFFSET_PARAM = "timeOffset"; + public static final String RESET_PUBLIC_KEY_PARAM = "resetPublicKey"; + + private static final Logger log = Logger.getLogger(AdapterActionsFilter.class); + @Override public void init(FilterConfig filterConfig) throws ServletException { @@ -45,16 +55,28 @@ public class AdapterActionsFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { + HttpServletRequest servletReq = (HttpServletRequest) request; HttpServletResponse servletResp = (HttpServletResponse) response; //Accept timeOffset as argument to enforce timeouts - String timeOffsetParam = request.getParameter("timeOffset"); - if (timeOffsetParam != null && !timeOffsetParam.isEmpty()) { - Time.setOffset(Integer.parseInt(timeOffsetParam)); - } + String timeOffsetParam = request.getParameter(TIME_OFFSET_PARAM); + String resetPublicKey = request.getParameter(RESET_PUBLIC_KEY_PARAM); - // Continue request - chain.doFilter(request, response); + if (timeOffsetParam != null && !timeOffsetParam.isEmpty()) { + int timeOffset = Integer.parseInt(timeOffsetParam); + log.infof("Time offset updated to %d for application %s", timeOffset, servletReq.getRequestURI()); + Time.setOffset(timeOffset); + writeResponse(servletResp, "Offset set successfully"); + } else if (resetPublicKey != null && !resetPublicKey.isEmpty()) { + AdapterDeploymentContext deploymentContext = (AdapterDeploymentContext) request.getServletContext().getAttribute(AdapterDeploymentContext.class.getName()); + KeycloakDeployment deployment = deploymentContext.resolveDeployment(null); + deployment.setPublicKeyLocator(new JWKPublicKeyLocator()); + log.infof("Restarted publicKey locator for application %s", servletReq.getRequestURI()); + writeResponse(servletResp, "PublicKeyLocator restarted successfully"); + } else { + // Continue request + chain.doFilter(request, response); + } } @@ -64,8 +86,9 @@ public class AdapterActionsFilter implements Filter { } private void writeResponse(HttpServletResponse response, String responseText) throws IOException { + response.setContentType("text/html"); PrintWriter writer = response.getWriter(); - writer.println(responseText); + writer.println("" + responseText + ""); writer.flush(); } } diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/AbstractShowTokensPage.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/AbstractShowTokensPage.java index d370dd049b..6cb1f37d5b 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/AbstractShowTokensPage.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/AbstractShowTokensPage.java @@ -23,6 +23,7 @@ import org.keycloak.representations.AccessToken; import org.keycloak.representations.RefreshToken; import org.keycloak.testsuite.page.AbstractPageWithInjectedUrl; import org.keycloak.util.JsonSerialization; +import org.openqa.selenium.NoSuchElementException; import org.openqa.selenium.WebElement; import org.openqa.selenium.support.FindBy; @@ -43,6 +44,8 @@ public abstract class AbstractShowTokensPage extends AbstractPageWithInjectedUrl return JsonSerialization.readValue(accessToken.getText(), AccessToken.class); } catch (IOException e) { e.printStackTrace(); + } catch (NoSuchElementException nsee) { + log.warn("No accessToken element found on the page"); } return null; @@ -53,7 +56,10 @@ public abstract class AbstractShowTokensPage extends AbstractPageWithInjectedUrl return JsonSerialization.readValue(refreshToken.getText(), RefreshToken.class); } catch (IOException e) { e.printStackTrace(); + } catch (NoSuchElementException nsee) { + log.warn("No idToken element found on the page"); } + return null; } } diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/BasicAuth.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/BasicAuth.java new file mode 100644 index 0000000000..8880d8313a --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/BasicAuth.java @@ -0,0 +1,61 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.keycloak.testsuite.adapter.page; + +import org.jboss.arquillian.container.test.api.OperateOnDeployment; +import org.jboss.arquillian.test.api.ArquillianResource; +import org.keycloak.testsuite.page.AbstractPageWithInjectedUrl; + +import javax.ws.rs.core.UriBuilder; +import java.net.URL; + +/** + * + * @author tkyjovsk + */ +public class BasicAuth extends AbstractPageWithInjectedUrl { + + public static final String DEPLOYMENT_NAME = "basic-auth"; + + @ArquillianResource + @OperateOnDeployment(DEPLOYMENT_NAME) + private URL url; + + @Override + public URL getInjectedUrl() { + //EAP6 URL fix + URL fixedUrl = createInjectedURL("basic-auth"); + return fixedUrl != null ? fixedUrl : url; + } + + @Override + public UriBuilder createUriBuilder() { + return super.createUriBuilder() + .userInfo("{user}:{password}") + .path("basic-auth") + .queryParam("value", "{value}"); + } + + public BasicAuth setTemplateValues(String user, String password, String value) { + setUriParameter("user", user); + setUriParameter("password", password); + setUriParameter("value", value); + return this; + } + +} diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/servlet/BasicAuthServlet.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/servlet/BasicAuthServlet.java new file mode 100644 index 0000000000..3c51343758 --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/servlet/BasicAuthServlet.java @@ -0,0 +1,27 @@ +package org.keycloak.testsuite.adapter.servlet; + +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.PrintWriter; + +/** + * @author mhajas + */ +@WebServlet("/basic-auth") +public class BasicAuthServlet extends HttpServlet { + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { + String value = req.getParameter("value"); + System.out.println("In BasicAuthServlet with value: " + value); + + resp.setContentType("text/plain"); + PrintWriter pw = resp.getWriter(); + pw.printf(value); + pw.flush(); + } + +} diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/AuthServerTestEnricher.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/AuthServerTestEnricher.java index 984ba7de4f..d958e6e8eb 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/AuthServerTestEnricher.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/AuthServerTestEnricher.java @@ -26,7 +26,7 @@ import org.jboss.arquillian.container.spi.Container; import org.jboss.arquillian.container.spi.ContainerRegistry; import org.jboss.arquillian.container.spi.event.StartContainer; import org.jboss.arquillian.container.spi.event.StartSuiteContainers; -import org.jboss.arquillian.container.test.api.ContainerController; +import org.jboss.arquillian.container.spi.event.StopContainer; import org.jboss.arquillian.core.api.Event; import org.jboss.arquillian.core.api.Instance; import org.jboss.arquillian.core.api.InstanceProducer; @@ -52,11 +52,10 @@ public class AuthServerTestEnricher { @Inject private Instance containerRegistry; - @Inject - private Instance containerController; - @Inject private Event startContainerEvent; + @Inject + private Event stopContainerEvent; private static final String AUTH_SERVER_CONTAINER_DEFAULT = "auth-server-undertow"; private static final String AUTH_SERVER_CONTAINER_PROPERTY = "auth.server.container"; @@ -65,8 +64,8 @@ public class AuthServerTestEnricher { private static final String AUTH_SERVER_CLUSTER_PROPERTY = "auth.server.cluster"; public static final boolean AUTH_SERVER_CLUSTER = Boolean.parseBoolean(System.getProperty(AUTH_SERVER_CLUSTER_PROPERTY, "false")); - private static final String MIGRATED_AUTH_SERVER_CONTAINER_PROPERTY = "migrated.auth.server.container"; - public static final String MIGRATED_AUTH_SERVER_CONTAINER = System.getProperty(MIGRATED_AUTH_SERVER_CONTAINER_PROPERTY); // == null if migration not enabled + private static final String MIGRATION_PROPERTY = "auth.server.jboss.migration"; + private static final Boolean MIGRATION_ENABLED = Boolean.parseBoolean(System.getProperty(MIGRATION_PROPERTY)); @Inject @SuiteScoped @@ -132,23 +131,19 @@ public class AuthServerTestEnricher { throw new RuntimeException(String.format("No auth server container matching '%sN' found in arquillian.xml.", authServerBackend)); } - if (MIGRATED_AUTH_SERVER_CONTAINER != null) { + if (MIGRATION_ENABLED) { // init migratedAuthServerInfo - if (MIGRATED_AUTH_SERVER_CONTAINER.startsWith("migrated-auth-server-")) { - for (ContainerInfo container : suiteContext.getContainers()) { - // migrated auth server - if (container.getQualifier().equals(MIGRATED_AUTH_SERVER_CONTAINER)) { - updateWithAuthServerInfo(container); - suiteContext.setMigratedAuthServerInfo(container); - } + for (ContainerInfo container : suiteContext.getContainers()) { + // migrated auth server + if (container.getQualifier().equals("auth-server-jboss-migration")) { + updateWithAuthServerInfo(container); + suiteContext.setMigratedAuthServerInfo(container); } - } else { - throw new IllegalArgumentException(String.format("Value of %s should start with 'migrated-auth-server-' prefix.", MIGRATED_AUTH_SERVER_CONTAINER_PROPERTY)); } // validate setup if (suiteContext.getMigratedAuthServerInfo() == null) { throw new RuntimeException(String.format("Migration test was enabled but no auth server from which to migrate was activated. " - + "A container matching '%s' needs to be enabled in arquillian.xml.", MIGRATED_AUTH_SERVER_CONTAINER)); + + "A container matching auth-server-jboss-migration needs to be enabled in arquillian.xml.")); } } @@ -178,7 +173,8 @@ public class AuthServerTestEnricher { public void stopMigratedContainer(@Observes(precedence = 1) StartSuiteContainers event) { if (suiteContext.isAuthServerMigrationEnabled()) { - containerController.get().stop(suiteContext.getAuthServerInfo().getQualifier()); + log.info("## STOP old container: " + suiteContext.getMigratedAuthServerInfo().getQualifier()); + stopContainerEvent.fire(new StopContainer(suiteContext.getMigratedAuthServerInfo().getArquillianContainer())); } } @@ -189,14 +185,6 @@ public class AuthServerTestEnricher { LogChecker.checkJBossServerLog(jbossHomePath); } } -// -// public void startAuthServerContainer(@Observes BeforeSuite event) { -// startContainerEvent.fire(new StartContainer(suiteContext.getAuthServerInfo().getArquillianContainer())); -// } -// -// public void stopAuthServerContainer(@Observes AfterSuite event) { -// containerController.get().stop(suiteContext.getAuthServerInfo().getQualifier()); -// } public void initializeTestContext(@Observes(precedence = 2) BeforeClass event) { TestContext testContext = new TestContext(suiteContext, event.getTestClass().getJavaClass()); diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/DeploymentArchiveProcessor.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/DeploymentArchiveProcessor.java index 9cd7625cc1..4af2988d1b 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/DeploymentArchiveProcessor.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/DeploymentArchiveProcessor.java @@ -49,8 +49,6 @@ import static org.keycloak.testsuite.util.IOUtil.*; */ public class DeploymentArchiveProcessor implements ApplicationArchiveProcessor { - public static final String REALM_KEY = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB"; - protected final Logger log = org.jboss.logging.Logger.getLogger(this.getClass()); private final boolean authServerSslRequired = Boolean.parseBoolean(System.getProperty("auth.server.ssl.required")); @@ -129,7 +127,6 @@ public class DeploymentArchiveProcessor implements ApplicationArchiveProcessor { // ac.setRealmKey(null); // TODO verify if realm key is required for relative scneario } else { adapterConfig.setAuthServerUrl(getAuthServerContextRoot() + "/auth"); - adapterConfig.setRealmKey(REALM_KEY); } if ("true".equals(System.getProperty("app.server.ssl.required"))) { diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/SuiteContext.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/SuiteContext.java index 929c23d2b7..d576b49a12 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/SuiteContext.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/SuiteContext.java @@ -98,6 +98,9 @@ public final class SuiteContext { for (ContainerInfo bInfo : getAuthServerBackendsInfo()) { containers += "Backend: " + bInfo + "\n"; } + if (isAuthServerMigrationEnabled()) { + containers += "Migrated from: " + System.getProperty("migrated.auth.server.version") + "\n"; + } return "SUITE CONTEXT:\n" + containers; } diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/migration/MigrationTestExecutionDecider.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/migration/MigrationTestExecutionDecider.java index 333c5ddef9..78d26d5059 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/migration/MigrationTestExecutionDecider.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/migration/MigrationTestExecutionDecider.java @@ -38,7 +38,7 @@ public class MigrationTestExecutionDecider implements TestExecutionDecider { if (migrationTest && migrationAnnotation != null) { String versionFrom = migrationAnnotation.versionFrom(); - if (migratedAuthServerVersion.equals(versionFrom)) { + if (versionFrom.equals(migratedAuthServerVersion)) { return ExecutionDecision.execute(); } else { return ExecutionDecision.dontExecute(method.getName() + "doesn't fit with migration version."); diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestApplicationResource.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestApplicationResource.java index 2efc26b3f7..2d277fd6e9 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestApplicationResource.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestApplicationResource.java @@ -20,7 +20,9 @@ package org.keycloak.testsuite.client.resources; import org.keycloak.representations.adapters.action.LogoutAction; import org.keycloak.representations.adapters.action.PushNotBeforeAction; import org.keycloak.representations.adapters.action.TestAvailabilityAction; +import org.keycloak.testsuite.rest.resource.TestingOIDCEndpointsApplicationResource; +import javax.ws.rs.Consumes; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.Path; @@ -53,4 +55,6 @@ public interface TestApplicationResource { @Path("/clear-admin-actions") Response clearAdminActions(); + @Path("/oidc-client-endpoints") + TestOIDCEndpointsApplicationResource oidcClientEndpoints(); } diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestApplicationResourceUrls.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestApplicationResourceUrls.java new file mode 100644 index 0000000000..88b7b38ea3 --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestApplicationResourceUrls.java @@ -0,0 +1,54 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.keycloak.testsuite.client.resources; + +import org.keycloak.testsuite.util.OAuthClient; + +import javax.ws.rs.core.UriBuilder; + +/** + * @author Marek Posolda + */ +public class TestApplicationResourceUrls { + + private static UriBuilder oidcClientEndpoints() { + return UriBuilder.fromUri(OAuthClient.AUTH_SERVER_ROOT) + .path(TestApplicationResource.class) + .path(TestApplicationResource.class, "oidcClientEndpoints"); + } + + public static String clientRequestUri() { + UriBuilder builder = oidcClientEndpoints() + .path(TestOIDCEndpointsApplicationResource.class, "getOIDCRequest"); + + return builder.build().toString(); + } + + public static String clientJwksUri() { + UriBuilder builder = oidcClientEndpoints() + .path(TestOIDCEndpointsApplicationResource.class, "getJwks"); + + return builder.build().toString(); + } + + public static String pairwiseSectorIdentifierUri() { + UriBuilder builder = oidcClientEndpoints() + .path(TestOIDCEndpointsApplicationResource.class, "getSectorIdentifierRedirectUris"); + return builder.build().toString(); + } +} diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestOIDCEndpointsApplicationResource.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestOIDCEndpointsApplicationResource.java new file mode 100644 index 0000000000..9c4f324967 --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestOIDCEndpointsApplicationResource.java @@ -0,0 +1,69 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.keycloak.testsuite.client.resources; + +import org.keycloak.jose.jwk.JSONWebKeySet; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.MediaType; +import java.util.List; +import java.util.Map; + +/** + * @author Marek Posolda + */ +public interface TestOIDCEndpointsApplicationResource { + + @GET + @Produces(MediaType.APPLICATION_JSON) + @Path("/generate-keys") + Map generateKeys(); + + + @GET + @Produces(MediaType.APPLICATION_JSON) + @Path("/get-jwks") + JSONWebKeySet getJwks(); + + + @GET + @Path("/set-oidc-request") + @Produces(org.keycloak.utils.MediaType.APPLICATION_JWT) + void setOIDCRequest(@QueryParam("realmName") String realmName, @QueryParam("clientId") String clientId, + @QueryParam("redirectUri") String redirectUri, @QueryParam("maxAge") String maxAge, + @QueryParam("jwaAlgorithm") String jwaAlgorithm); + + @GET + @Path("/get-oidc-request") + @Produces(org.keycloak.utils.MediaType.APPLICATION_JWT) + String getOIDCRequest(); + + @GET + @Path("/set-sector-identifier-redirect-uris") + @Produces(MediaType.APPLICATION_JSON) + void setSectorIdentifierRedirectUris(@QueryParam("redirectUris") List redirectUris); + + @GET + @Path("/get-sector-identifier-redirect-uris") + @Produces(MediaType.APPLICATION_JSON) + List getSectorIdentifierRedirectUris(); + +} diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestingExportImportResource.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestingExportImportResource.java new file mode 100644 index 0000000000..27fa3604c4 --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestingExportImportResource.java @@ -0,0 +1,93 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.keycloak.testsuite.client.resources; + +import javax.ws.rs.Consumes; +import javax.ws.rs.GET; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +/** + * @author Marek Posolda + */ +public interface TestingExportImportResource { + + @GET + @Path("/run-import") + @Produces(MediaType.APPLICATION_JSON) + public Response runImport(); + + @GET + @Path("/run-export") + @Produces(MediaType.APPLICATION_JSON) + public Response runExport(); + + @GET + @Path("/get-users-per-file") + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public Integer getUsersPerFile(); + + @PUT + @Path("/set-users-per-file") + @Consumes(MediaType.APPLICATION_JSON) + public void setUsersPerFile(@QueryParam("usersPerFile") Integer usersPerFile); + + @GET + @Path("/get-dir") + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public String getDir(); + + @PUT + @Path("/set-dir") + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public String setDir(@QueryParam("dir") String dir); + + @PUT + @Path("/export-import-provider") + @Consumes(MediaType.APPLICATION_JSON) + public void setProvider(@QueryParam("exportImportProvider") String exportImportProvider); + + @PUT + @Path("/export-import-file") + @Consumes(MediaType.APPLICATION_JSON) + public void setFile(@QueryParam("file") String file); + + @PUT + @Path("/export-import-action") + @Consumes(MediaType.APPLICATION_JSON) + public void setAction(@QueryParam("exportImportAction") String exportImportAction); + + @PUT + @Path("/set-realm-name") + @Consumes(MediaType.APPLICATION_JSON) + public void setRealmName(@QueryParam("realmName") String realmName); + + @GET + @Path("/get-test-dir") + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public String getExportImportTestDirectory(); + +} diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestingResource.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestingResource.java index fcf5d8317a..0dbcd58f55 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestingResource.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestingResource.java @@ -17,8 +17,8 @@ package org.keycloak.testsuite.client.resources; -import java.util.Date; import java.util.List; + import org.keycloak.representations.idm.AdminEventRepresentation; import org.keycloak.representations.idm.AuthenticationFlowRepresentation; import org.keycloak.representations.idm.EventRepresentation; @@ -37,7 +37,6 @@ import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import java.util.Map; import org.jboss.resteasy.annotations.cache.NoCache; -import org.keycloak.exportimport.ExportImportManager; /** * @author Marko Strukelj @@ -205,16 +204,6 @@ public interface TestingResource { @Path("/update-pass-through-auth-state") @Produces(MediaType.APPLICATION_JSON) AuthenticatorState updateAuthenticator(AuthenticatorState state); - - @GET - @Path("/run-import") - @Produces(MediaType.APPLICATION_JSON) - public Response runImport(); - - @GET - @Path("/run-export") - @Produces(MediaType.APPLICATION_JSON) - public Response runExport(); @GET @Path("/valid-credentials") @@ -250,53 +239,7 @@ public interface TestingResource { @Produces(MediaType.APPLICATION_JSON) public UserRepresentation getUserByServiceAccountClient(@QueryParam("realmName") String realmName, @QueryParam("clientId") String clientId); + @Path("export-import") + TestingExportImportResource exportImport(); - @GET - @Path("/get-users-per-file") - @Consumes(MediaType.APPLICATION_JSON) - @Produces(MediaType.APPLICATION_JSON) - public Integer getUsersPerFile(); - - @PUT - @Path("/set-users-per-file") - @Consumes(MediaType.APPLICATION_JSON) - public void setUsersPerFile(@QueryParam("usersPerFile") Integer usersPerFile); - - @GET - @Path("/get-dir") - @Consumes(MediaType.APPLICATION_JSON) - @Produces(MediaType.APPLICATION_JSON) - public String getDir(); - - @PUT - @Path("/set-dir") - @Consumes(MediaType.APPLICATION_JSON) - @Produces(MediaType.APPLICATION_JSON) - public String setDir(@QueryParam("dir") String dir); - - @PUT - @Path("/export-import-provider") - @Consumes(MediaType.APPLICATION_JSON) - public void setProvider(@QueryParam("exportImportProvider") String exportImportProvider); - - @PUT - @Path("/export-import-file") - @Consumes(MediaType.APPLICATION_JSON) - public void setFile(@QueryParam("file") String file); - - @PUT - @Path("/export-import-action") - @Consumes(MediaType.APPLICATION_JSON) - public void setAction(@QueryParam("exportImportAction") String exportImportAction); - - @PUT - @Path("/set-realm-name") - @Consumes(MediaType.APPLICATION_JSON) - public void setRealmName(@QueryParam("realmName") String realmName); - - @GET - @Path("/get-test-dir") - @Consumes(MediaType.APPLICATION_JSON) - @Produces(MediaType.APPLICATION_JSON) - public String getExportImportTestDirectory(); } diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/AccountUpdateProfilePage.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/AccountUpdateProfilePage.java index 2159c2f6ff..2a77847fac 100755 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/AccountUpdateProfilePage.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/AccountUpdateProfilePage.java @@ -17,7 +17,9 @@ package org.keycloak.testsuite.pages; +import org.keycloak.models.Constants; import org.keycloak.services.resources.RealmsResource; +import org.openqa.selenium.By; import org.openqa.selenium.WebElement; import org.openqa.selenium.support.FindBy; @@ -96,6 +98,14 @@ public class AccountUpdateProfilePage extends AbstractAccountPage { submitButton.click(); } + public void updateAttribute(String attrName, String attrValue) { + WebElement attrElement = findAttributeInputElement(attrName); + attrElement.clear(); + attrElement.sendKeys(attrValue); + submitButton.click(); + } + + public void clickCancel() { cancelButton.click(); } @@ -117,6 +127,11 @@ public class AccountUpdateProfilePage extends AbstractAccountPage { return emailInput.getAttribute("value"); } + public String getAttribute(String attrName) { + WebElement attrElement = findAttributeInputElement(attrName); + return attrElement.getAttribute("value"); + } + public boolean isCurrent() { return driver.getTitle().contains("Account Management") && driver.getPageSource().contains("Edit Account"); } @@ -140,4 +155,9 @@ public class AccountUpdateProfilePage extends AbstractAccountPage { public boolean isPasswordUpdateSupported() { return driver.getPageSource().contains(getPath() + "/password"); } + + private WebElement findAttributeInputElement(String attrName) { + String attrId = Constants.USER_ATTRIBUTES_PREFIX + attrName; + return driver.findElement(By.id(attrId)); + } } diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/LoginPage.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/LoginPage.java index 78913d6942..94a8fb6b97 100755 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/LoginPage.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/LoginPage.java @@ -71,6 +71,9 @@ public class LoginPage extends AbstractPage { @FindBy(className = "alert-info") private WebElement loginInfoMessage; + @FindBy(className = "instruction") + private WebElement instruction; + @FindBy(id = "kc-current-locale-link") private WebElement languageText; @@ -128,6 +131,10 @@ public class LoginPage extends AbstractPage { return loginErrorMessage != null ? loginErrorMessage.getText() : null; } + public String getInstruction() { + return instruction != null ? instruction.getText() : null; + } + public String getSuccessMessage() { return loginSuccessMessage != null ? loginSuccessMessage.getText() : null; } diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/OAuthClient.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/OAuthClient.java index 586351d1fd..2253828bd5 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/OAuthClient.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/OAuthClient.java @@ -105,6 +105,10 @@ public class OAuthClient { private String nonce; + private String request; + + private String requestUri; + private Map publicKeys = new HashMap<>(); public void init(Keycloak adminClient, WebDriver driver) { @@ -121,6 +125,9 @@ public class OAuthClient { clientSessionState = null; clientSessionHost = null; maxAge = null; + nonce = null; + request = null; + requestUri = null; } public AuthorizationEndpointResponse doLogin(String username, String password) { @@ -536,6 +543,12 @@ public class OAuthClient { if (maxAge != null) { b.queryParam(OIDCLoginProtocol.MAX_AGE_PARAM, maxAge); } + if (request != null) { + b.queryParam(OIDCLoginProtocol.REQUEST_PARAM, request); + } + if (requestUri != null) { + b.queryParam(OIDCLoginProtocol.REQUEST_URI_PARAM, requestUri); + } return b.build(realm).toString(); } @@ -644,6 +657,16 @@ public class OAuthClient { return this; } + public OAuthClient request(String request) { + this.request = request; + return this; + } + + public OAuthClient requestUri(String requestUri) { + this.requestUri = requestUri; + return this; + } + public String getRealm() { return realm; } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountTest.java index e4dfe0eea2..35d11d15f4 100755 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountTest.java @@ -377,12 +377,15 @@ public class AccountTest extends TestRealmKeycloakTest { } @Test - public void changeProfile() { + public void changeProfile() throws Exception { + setEditUsernameAllowed(false); + profilePage.open(); loginPage.login("test-user@localhost", "password"); events.expectLogin().client("account").detail(Details.REDIRECT_URI, ACCOUNT_REDIRECT).assertEvent(); + Assert.assertEquals("test-user@localhost", profilePage.getUsername()); Assert.assertEquals("Tom", profilePage.getFirstName()); Assert.assertEquals("Brady", profilePage.getLastName()); Assert.assertEquals("test-user@localhost", profilePage.getEmail()); @@ -391,6 +394,7 @@ public class AccountTest extends TestRealmKeycloakTest { profilePage.updateProfile("", "New last", "new@email.com"); Assert.assertEquals("Please specify first name.", profilePage.getError()); + Assert.assertEquals("test-user@localhost", profilePage.getUsername()); Assert.assertEquals("", profilePage.getFirstName()); Assert.assertEquals("New last", profilePage.getLastName()); Assert.assertEquals("new@email.com", profilePage.getEmail()); @@ -417,6 +421,7 @@ public class AccountTest extends TestRealmKeycloakTest { profilePage.clickCancel(); + Assert.assertEquals("test-user@localhost", profilePage.getUsername()); Assert.assertEquals("Tom", profilePage.getFirstName()); Assert.assertEquals("Brady", profilePage.getLastName()); Assert.assertEquals("test-user@localhost", profilePage.getEmail()); @@ -426,6 +431,7 @@ public class AccountTest extends TestRealmKeycloakTest { profilePage.updateProfile("New first", "New last", "new@email.com"); Assert.assertEquals("Your account has been updated.", profilePage.getSuccess()); + Assert.assertEquals("test-user@localhost", profilePage.getUsername()); Assert.assertEquals("New first", profilePage.getFirstName()); Assert.assertEquals("New last", profilePage.getLastName()); Assert.assertEquals("new@email.com", profilePage.getEmail()); @@ -436,18 +442,21 @@ public class AccountTest extends TestRealmKeycloakTest { // reset user for other tests profilePage.updateProfile("Tom", "Brady", "test-user@localhost"); events.clear(); + + // Revert + setEditUsernameAllowed(true); } - private void setEditUsernameAllowed() { + private void setEditUsernameAllowed(boolean allowed) { RealmRepresentation testRealm = testRealm().toRepresentation(); - testRealm.setEditUsernameAllowed(true); + testRealm.setEditUsernameAllowed(allowed); testRealm().update(testRealm); } @Test public void changeUsername() { // allow to edit the username in realm - setEditUsernameAllowed(); + setEditUsernameAllowed(true); profilePage.open(); loginPage.login("test-user@localhost", "password"); @@ -504,7 +513,7 @@ public class AccountTest extends TestRealmKeycloakTest { @Test public void changeUsernameLoginWithOldUsername() { addUser("change-username", "change-username@localhost"); - setEditUsernameAllowed(); + setEditUsernameAllowed(true); profilePage.open(); loginPage.login("change-username", "password"); @@ -530,7 +539,7 @@ public class AccountTest extends TestRealmKeycloakTest { @Test public void changeEmailLoginWithOldEmail() { addUser("change-email", "change-username@localhost"); - setEditUsernameAllowed(); + setEditUsernameAllowed(true); profilePage.open(); loginPage.login("change-username@localhost", "password"); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/custom/CustomThemeTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/custom/CustomThemeTest.java new file mode 100644 index 0000000000..810d9c2084 --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/custom/CustomThemeTest.java @@ -0,0 +1,86 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.keycloak.testsuite.account.custom; + +import org.jboss.arquillian.graphene.page.Page; +import org.junit.Assert; +import org.junit.Rule; +import org.junit.Test; +import org.keycloak.events.Details; +import org.keycloak.events.EventType; +import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.representations.idm.UserRepresentation; +import org.keycloak.testsuite.AssertEvents; +import org.keycloak.testsuite.TestRealmKeycloakTest; +import org.keycloak.testsuite.account.AccountTest; +import org.keycloak.testsuite.pages.AccountUpdateProfilePage; +import org.keycloak.testsuite.pages.LoginPage; +import org.keycloak.testsuite.util.RealmBuilder; +import org.keycloak.testsuite.util.UserBuilder; + +/** + * @author Marek Posolda + */ +public class CustomThemeTest extends TestRealmKeycloakTest { + + @Override + public void configureTestRealm(RealmRepresentation testRealm) { + testRealm.setAccountTheme("address"); + + UserRepresentation user2 = UserBuilder.create() + .enabled(true) + .username("test-user-no-access@localhost") + .email("test-user-no-access@localhost") + .password("password") + .build(); + + RealmBuilder.edit(testRealm) + .user(user2); + } + + @Rule + public AssertEvents events = new AssertEvents(this); + + @Page + protected LoginPage loginPage; + + @Page + protected AccountUpdateProfilePage profilePage; + + // KEYCLOAK-3494 + @Test + public void changeProfile() throws Exception { + profilePage.open(); + loginPage.login("test-user@localhost", "password"); + + events.expectLogin().client("account").detail(Details.REDIRECT_URI, AccountTest.ACCOUNT_REDIRECT).assertEvent(); + + Assert.assertEquals("test-user@localhost", profilePage.getEmail()); + Assert.assertEquals("", profilePage.getAttribute("street")); + + profilePage.updateAttribute("street", "Elm 1"); + Assert.assertEquals("Elm 1", profilePage.getAttribute("street")); + + profilePage.updateAttribute("street", "Elm 2"); + Assert.assertEquals("Elm 2", profilePage.getAttribute("street")); + + events.expectAccount(EventType.UPDATE_PROFILE).assertEvent(); + } + + +} diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/AbstractExampleAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/AbstractExampleAdapterTest.java index 9e7f4f1355..065477a407 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/AbstractExampleAdapterTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/AbstractExampleAdapterTest.java @@ -17,11 +17,6 @@ package org.keycloak.testsuite.adapter; -import java.io.File; -import java.io.IOException; -import java.net.URL; -import java.nio.file.Paths; - import org.apache.commons.io.IOUtils; import org.jboss.shrinkwrap.api.ShrinkWrap; import org.jboss.shrinkwrap.api.asset.StringAsset; @@ -29,6 +24,11 @@ import org.jboss.shrinkwrap.api.spec.JavaArchive; import org.jboss.shrinkwrap.api.spec.WebArchive; import org.junit.Assert; +import java.io.File; +import java.io.IOException; +import java.net.URL; +import java.nio.file.Paths; + /** * * @author tkyjovsk @@ -50,11 +50,11 @@ public abstract class AbstractExampleAdapterTest extends AbstractAdapterTest { Assert.assertNotNull("Property ${examples.version.suffix} must bet set.", EXAMPLES_VERSION_SUFFIX); System.out.println(EXAMPLES_VERSION_SUFFIX); + EXAMPLES_HOME_DIR = EXAMPLES_HOME + "/example-realms"; + if (!System.getProperty("unpacked.container.folder.name","").isEmpty()) { - EXAMPLES_HOME_DIR = EXAMPLES_HOME + "/" + System.getProperty("unpacked.container.folder.name","") + "-examples"; TEST_APPS_HOME_DIR = EXAMPLES_HOME + "/" + System.getProperty("unpacked.container.folder.name","") + "-test-apps"; } else { - EXAMPLES_HOME_DIR = EXAMPLES_HOME + "/keycloak-examples-" + EXAMPLES_VERSION_SUFFIX; TEST_APPS_HOME_DIR = EXAMPLES_HOME + "/Keycloak-" + EXAMPLES_VERSION_SUFFIX + "-test-apps"; } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/AbstractServletsAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/AbstractServletsAdapterTest.java index 127c863fa8..a40275f727 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/AbstractServletsAdapterTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/AbstractServletsAdapterTest.java @@ -22,6 +22,7 @@ import org.jboss.shrinkwrap.api.ShrinkWrap; import org.jboss.shrinkwrap.api.asset.StringAsset; import org.jboss.shrinkwrap.api.spec.WebArchive; import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.testsuite.adapter.filter.AdapterActionsFilter; import org.keycloak.testsuite.util.WaitUtils; import org.openqa.selenium.By; @@ -113,7 +114,7 @@ public abstract class AbstractServletsAdapterTest extends AbstractAdapterTest { protected void setAdapterAndServerTimeOffset(int timeOffset, String servletUri) { setTimeOffset(timeOffset); String timeOffsetUri = UriBuilder.fromUri(servletUri) - .queryParam("timeOffset", timeOffset) + .queryParam(AdapterActionsFilter.TIME_OFFSET_PARAM, timeOffset) .build().toString(); driver.navigate().to(timeOffsetUri); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/AbstractDemoExampleAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/AbstractDemoExampleAdapterTest.java index b1df6e0a34..4db4a3c46a 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/AbstractDemoExampleAdapterTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/AbstractDemoExampleAdapterTest.java @@ -98,7 +98,7 @@ public abstract class AbstractDemoExampleAdapterTest extends AbstractExampleAdap @Override public void addAdapterTestRealms(List testRealms) { testRealms.add( - loadRealm(new File(EXAMPLES_HOME_DIR + "/preconfigured-demo/testrealm.json"))); + loadRealm(new File(EXAMPLES_HOME_DIR + "/demo-template/testrealm.json"))); } @Override diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/AbstractFuseExampleAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/AbstractFuseExampleAdapterTest.java index cc137242fe..b0583fb70a 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/AbstractFuseExampleAdapterTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/AbstractFuseExampleAdapterTest.java @@ -17,22 +17,23 @@ package org.keycloak.testsuite.adapter.example; -import org.keycloak.testsuite.adapter.AbstractExampleAdapterTest; -import java.io.File; -import java.util.List; import org.jboss.arquillian.graphene.page.Page; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; import org.junit.Test; import org.keycloak.representations.idm.RealmRepresentation; -import org.keycloak.testsuite.auth.page.account.Account; -import static org.keycloak.testsuite.util.IOUtil.loadRealm; -import static org.keycloak.testsuite.adapter.AbstractExampleAdapterTest.EXAMPLES_HOME_DIR; +import org.keycloak.testsuite.adapter.AbstractExampleAdapterTest; import org.keycloak.testsuite.adapter.page.fuse.AdminInterface; import org.keycloak.testsuite.adapter.page.fuse.CustomerListing; import org.keycloak.testsuite.adapter.page.fuse.CustomerPortalFuseExample; import org.keycloak.testsuite.adapter.page.fuse.ProductPortalFuseExample; +import org.keycloak.testsuite.auth.page.account.Account; + +import java.io.File; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import static org.keycloak.testsuite.auth.page.AuthRealm.DEMO; +import static org.keycloak.testsuite.util.IOUtil.loadRealm; import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlStartsWith; import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlStartsWithLoginUrlOf; import static org.keycloak.testsuite.util.WaitUtils.pause; diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractPhotozExampleAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractPhotozExampleAdapterTest.java index 66702a98a0..9d25421724 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractPhotozExampleAdapterTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractPhotozExampleAdapterTest.java @@ -23,13 +23,7 @@ import org.jboss.arquillian.test.api.ArquillianResource; import org.jboss.shrinkwrap.api.spec.WebArchive; import org.junit.Before; import org.junit.Test; -import org.keycloak.admin.client.resource.AuthorizationResource; -import org.keycloak.admin.client.resource.ClientResource; -import org.keycloak.admin.client.resource.ClientsResource; -import org.keycloak.admin.client.resource.ResourcesResource; -import org.keycloak.admin.client.resource.RoleResource; -import org.keycloak.admin.client.resource.UserResource; -import org.keycloak.admin.client.resource.UsersResource; +import org.keycloak.admin.client.resource.*; import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.RoleRepresentation; @@ -45,11 +39,7 @@ import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.function.Predicate; import java.util.stream.Collectors; @@ -80,8 +70,15 @@ public abstract class AbstractPhotozExampleAdapterTest extends AbstractExampleAd } @Before - public void beforePhotozExampleAdapterTest() { + public void beforePhotozExampleAdapterTest() throws FileNotFoundException { deleteAllCookiesForClientPage(); + + for (PolicyRepresentation policy : getAuthorizationResource().policies().policies()) { + if ("Only Owner Policy".equals(policy.getName())) { + policy.getConfig().put("mavenArtifactVersion", System.getProperty("project.version")); + getAuthorizationResource().policies().policy(policy.getId()).update(policy); + } + } } @Override diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractDemoServletsAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractDemoServletsAdapterTest.java index 083867f514..065de7768d 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractDemoServletsAdapterTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractDemoServletsAdapterTest.java @@ -17,6 +17,7 @@ package org.keycloak.testsuite.adapter.servlet; +import org.apache.commons.io.FileUtils; import org.jboss.arquillian.container.test.api.Deployment; import org.jboss.arquillian.graphene.page.Page; import org.jboss.shrinkwrap.api.spec.WebArchive; @@ -25,19 +26,31 @@ import org.junit.Before; import org.junit.Ignore; import org.junit.Test; import org.keycloak.OAuth2Constants; +import org.keycloak.admin.client.resource.ClientResource; import org.keycloak.common.Version; import org.keycloak.common.util.Time; import org.keycloak.constants.AdapterConstants; +import org.keycloak.models.Constants; import org.keycloak.protocol.oidc.OIDCLoginProtocol; import org.keycloak.protocol.oidc.OIDCLoginProtocolService; import org.keycloak.representations.AccessToken; import org.keycloak.representations.VersionRepresentation; +import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.representations.idm.UserRepresentation; import org.keycloak.testsuite.adapter.AbstractServletsAdapterTest; import org.keycloak.testsuite.adapter.filter.AdapterActionsFilter; import org.keycloak.testsuite.adapter.page.*; +import org.keycloak.testsuite.admin.ApiUtil; +import org.keycloak.testsuite.auth.page.account.Applications; +import org.keycloak.testsuite.auth.page.login.OAuthGrant; +import org.keycloak.testsuite.console.page.events.Config; +import org.keycloak.testsuite.console.page.events.LoginEvents; +import org.keycloak.testsuite.util.URLAssert; import org.keycloak.testsuite.util.URLUtils; import org.keycloak.util.BasicAuthHelper; +import org.openqa.selenium.By; +import org.openqa.selenium.WebElement; import javax.ws.rs.client.Client; import javax.ws.rs.client.ClientBuilder; @@ -46,15 +59,23 @@ import javax.ws.rs.client.WebTarget; import javax.ws.rs.core.Form; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.Response; +import javax.ws.rs.core.UriBuilder; +import java.io.File; +import java.io.IOException; import java.net.URI; +import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import static org.junit.Assert.*; +import static org.keycloak.testsuite.auth.page.AuthRealm.DEMO; import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlEquals; import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlStartsWithLoginUrlOf; import static org.keycloak.testsuite.util.WaitUtils.pause; +import static org.keycloak.testsuite.util.WaitUtils.waitUntilElement; /** * @@ -78,6 +99,16 @@ public abstract class AbstractDemoServletsAdapterTest extends AbstractServletsAd private InputPortal inputPortal; @Page private TokenMinTTLPage tokenMinTTLPage; + @Page + private OAuthGrant oAuthGrantPage; + @Page + private Applications applicationsPage; + @Page + private LoginEvents loginEventsPage; + @Page + private BasicAuth basicAuthPage; + @Page + private Config configPage; @Deployment(name = CustomerPortal.DEPLOYMENT_NAME) protected static WebArchive customerPortal() { @@ -119,6 +150,20 @@ public abstract class AbstractDemoServletsAdapterTest extends AbstractServletsAd return servletDeployment(TokenMinTTLPage.DEPLOYMENT_NAME, AdapterActionsFilter.class, AbstractShowTokensServlet.class, TokenMinTTLServlet.class, ErrorServlet.class); } + @Deployment(name = BasicAuth.DEPLOYMENT_NAME) + protected static WebArchive basicAuth() { + return servletDeployment(BasicAuth.DEPLOYMENT_NAME, BasicAuthServlet.class); + } + + @Override + public void setDefaultPageUriParameters() { + super.setDefaultPageUriParameters(); + configPage.setConsoleRealm(DEMO); + loginEventsPage.setConsoleRealm(DEMO); + applicationsPage.setAuthRealm(DEMO); + loginEventsPage.setConsoleRealm(DEMO); + } + @Before public void beforeDemoServletsAdapterTest() { // Delete all cookies from token-min-ttl page to be sure we are logged out @@ -166,6 +211,59 @@ public abstract class AbstractDemoServletsAdapterTest extends AbstractServletsAd client.close(); } + @Test + public void testRealmKeyRotationWithNewKeyDownload() throws Exception { + // Login success first + tokenMinTTLPage.navigateTo(); + testRealmLoginPage.form().waitForUsernameInputPresent(); + assertCurrentUrlStartsWithLoginUrlOf(testRealmPage); + testRealmLoginPage.form().login("bburke@redhat.com", "password"); + assertCurrentUrlEquals(tokenMinTTLPage); + + AccessToken token = tokenMinTTLPage.getAccessToken(); + Assert.assertEquals("bburke@redhat.com", token.getPreferredUsername()); + + // Logout + String logoutUri = OIDCLoginProtocolService.logoutUrl(authServerPage.createUriBuilder()) + .queryParam(OAuth2Constants.REDIRECT_URI, tokenMinTTLPage.toString()) + .build("demo").toString(); + driver.navigate().to(logoutUri); + + // Generate new realm key + RealmRepresentation realmRep = testRealmResource().toRepresentation(); + String oldPublicKey = realmRep.getPublicKey(); + String oldPrivateKey = realmRep.getPrivateKey(); + realmRep.setPublicKey(Constants.GENERATE); + testRealmResource().update(realmRep); + + // Try to login again. It should fail now + tokenMinTTLPage.navigateTo(); + testRealmLoginPage.form().waitForUsernameInputPresent(); + assertCurrentUrlStartsWithLoginUrlOf(testRealmPage); + testRealmLoginPage.form().login("bburke@redhat.com", "password"); + URLAssert.assertCurrentUrlStartsWith(driver, tokenMinTTLPage.getInjectedUrl().toString()); + assertNull(tokenMinTTLPage.getAccessToken()); + + String adapterActionsUrl = tokenMinTTLPage.toString() + "/unsecured/foo"; + setAdapterAndServerTimeOffset(300, adapterActionsUrl); + + // Try to login. Should work now due to realm key change + tokenMinTTLPage.navigateTo(); + assertCurrentUrlEquals(tokenMinTTLPage); + token = tokenMinTTLPage.getAccessToken(); + Assert.assertEquals("bburke@redhat.com", token.getPreferredUsername()); + driver.navigate().to(logoutUri); + + // Revert public keys change + String timeOffsetUri = UriBuilder.fromUri(adapterActionsUrl) + .queryParam(AdapterActionsFilter.RESET_PUBLIC_KEY_PARAM, "true") + .build().toString(); + driver.navigate().to(timeOffsetUri); + waitUntilElement(By.tagName("body")).is().visible(); + + setAdapterAndServerTimeOffset(0, adapterActionsUrl); + } + @Test public void testLoginSSOAndLogout() { // test login to customer-portal which does a bearer request to customer-db @@ -261,7 +359,7 @@ public abstract class AbstractDemoServletsAdapterTest extends AbstractServletsAd demoRealmRep.setSsoSessionIdleTimeout(1); testRealmResource().update(demoRealmRep); - pause(2000); + pause(2000); productPortal.navigateTo(); assertCurrentUrlStartsWithLoginUrlOf(testRealmPage); @@ -323,6 +421,10 @@ public abstract class AbstractDemoServletsAdapterTest extends AbstractServletsAd demoRealmRep.setSsoSessionIdleTimeout(originalIdle); testRealmResource().update(demoRealmRep); + + String logoutUri = OIDCLoginProtocolService.logoutUrl(authServerPage.createUriBuilder()) + .queryParam(OAuth2Constants.REDIRECT_URI, securePortal.toString()).build("demo").toString(); + driver.navigate().to(logoutUri); } @Test @@ -444,6 +546,7 @@ public abstract class AbstractDemoServletsAdapterTest extends AbstractServletsAd // Sets 5 minutes offset and assert access token will be still the same setAdapterAndServerTimeOffset(300, tokenMinTTLPage.toString()); + tokenMinTTLPage.navigateTo(); token = tokenMinTTLPage.getAccessToken(); int tokenIssued2 = token.getIssuedAt(); Assert.assertEquals(tokenIssued1, tokenIssued2); @@ -451,6 +554,7 @@ public abstract class AbstractDemoServletsAdapterTest extends AbstractServletsAd // Sets 9 minutes offset and assert access token will be refreshed (accessTokenTimeout is 10 minutes, token-min-ttl is 2 minutes. Hence 8 minutes or more should be sufficient) setAdapterAndServerTimeOffset(540, tokenMinTTLPage.toString()); + tokenMinTTLPage.navigateTo(); token = tokenMinTTLPage.getAccessToken(); int tokenIssued3 = token.getIssuedAt(); Assert.assertTrue(tokenIssued3 > tokenIssued1); @@ -486,5 +590,193 @@ public abstract class AbstractDemoServletsAdapterTest extends AbstractServletsAd setAdapterAndServerTimeOffset(0, tokenMinTTLPage.toString()); } + @Test + public void testBasicAuth() { + String value = "hello"; + Client client = ClientBuilder.newClient(); + + Response response = client.target(basicAuthPage + .setTemplateValues("mposolda", "password", value).buildUri()).request().get(); + + assertEquals(200, response.getStatus()); + assertEquals(value, response.readEntity(String.class)); + response.close(); + + response = client.target(basicAuthPage + .setTemplateValues("invalid-user", "password", value).buildUri()).request().get(); + assertEquals(401, response.getStatus()); + String readResponse = response.readEntity(String.class); + assertTrue(readResponse.contains("Unauthorized") || readResponse.contains("Status 401")); + response.close(); + + response = client.target(basicAuthPage + .setTemplateValues("admin", "invalid-password", value).buildUri()).request().get(); + assertEquals(401, response.getStatus()); + readResponse = response.readEntity(String.class); + assertTrue(readResponse.contains("Unauthorized") || readResponse.contains("Status 401")); + response.close(); + + client.close(); + } + + @Test + public void grantServerBasedApp() { + ClientResource clientResource = ApiUtil.findClientResourceByClientId(testRealmResource(), "customer-portal"); + ClientRepresentation client = clientResource.toRepresentation(); + client.setConsentRequired(true); + clientResource.update(client); + + RealmRepresentation realm = testRealmResource().toRepresentation(); + realm.setEventsEnabled(true); + realm.setEnabledEventTypes(Arrays.asList("REVOKE_GRANT", "LOGIN")); + testRealmResource().update(realm); + + customerPortal.navigateTo(); + + loginPage.form().login("bburke@redhat.com", "password"); + + assertTrue(oAuthGrantPage.isCurrent()); + + oAuthGrantPage.accept(); + + waitUntilElement(By.xpath("//body")).text().contains("Bill Burke"); + waitUntilElement(By.xpath("//body")).text().contains("Stian Thorgersen"); + + applicationsPage.navigateTo(); + applicationsPage.revokeGrantForApplication("customer-portal"); + + customerPortal.navigateTo(); + + assertTrue(oAuthGrantPage.isCurrent()); + + loginEventsPage.navigateTo(); + + if (!testContext.isAdminLoggedIn()) { + loginPage.form().login(adminUser); + testContext.setAdminLoggedIn(true); + } + + loginEventsPage.table().filter(); + loginEventsPage.table().filterForm().addEventType("REVOKE_GRANT"); + loginEventsPage.table().update(); + + List resultList = loginEventsPage.table().rows(); + + assertEquals(1, resultList.size()); + + resultList.get(0).findElement(By.xpath(".//td[text()='REVOKE_GRANT']")); + resultList.get(0).findElement(By.xpath(".//td[text()='Client']/../td[text()='account']")); + resultList.get(0).findElement(By.xpath(".//td[text()='IP Address']/../td[text()='127.0.0.1' or text()='0:0:0:0:0:0:0:1']")); + resultList.get(0).findElement(By.xpath(".//td[text()='revoked_client']/../td[text()='customer-portal']")); + + loginEventsPage.table().reset(); + loginEventsPage.table().filterForm().addEventType("LOGIN"); + loginEventsPage.table().update(); + resultList = loginEventsPage.table().rows(); + + assertEquals(1, resultList.size()); + + resultList.get(0).findElement(By.xpath(".//td[text()='LOGIN']")); + resultList.get(0).findElement(By.xpath(".//td[text()='Client']/../td[text()='customer-portal']")); + resultList.get(0).findElement(By.xpath(".//td[text()='IP Address']/../td[text()='127.0.0.1' or text()='0:0:0:0:0:0:0:1']")); + resultList.get(0).findElement(By.xpath(".//td[text()='username']/../td[text()='bburke@redhat.com']")); + resultList.get(0).findElement(By.xpath(".//td[text()='consent']/../td[text()='consent_granted']")); + + configPage.navigateTo(); + configPage.form().clearLoginEvents(); + driver.findElement(By.xpath("//div[@class='modal-dialog']//button[text()='Delete']")).click(); + } + + @Test + public void historyOfAccessResourceTest() throws IOException { + RealmRepresentation realm = testRealmResource().toRepresentation(); + realm.setEventsEnabled(true); + realm.setEnabledEventTypes(Arrays.asList("LOGIN", "LOGIN_ERROR", "LOGOUT", "CODE_TO_TOKEN")); + testRealmResource().update(realm); + + customerPortal.navigateTo(); + + testRealmLoginPage.form().login("bburke@redhat.com", "password"); + + waitUntilElement(By.xpath("//body")).text().contains("Bill Burke"); + waitUntilElement(By.xpath("//body")).text().contains("Stian Thorgersen"); + + driver.navigate().to(testRealmPage.getOIDCLogoutUrl() + "?redirect_uri=" + customerPortal); + + loginEventsPage.navigateTo(); + + if (!testContext.isAdminLoggedIn()) { + loginPage.form().login(adminUser); + testContext.setAdminLoggedIn(true); + } + + loginEventsPage.table().filter(); + loginEventsPage.table().filterForm().addEventType("LOGOUT"); + loginEventsPage.table().update(); + + List resultList = loginEventsPage.table().rows(); + + assertEquals(1, resultList.size()); + + resultList.get(0).findElement(By.xpath(".//td[text()='LOGOUT']")); + resultList.get(0).findElement(By.xpath(".//td[text()='Client']/../td[text()='']")); + resultList.get(0).findElement(By.xpath(".//td[text()='IP Address']/../td[text()='127.0.0.1' or text()='0:0:0:0:0:0:0:1']")); + + loginEventsPage.table().reset(); + loginEventsPage.table().filterForm().addEventType("LOGIN"); + loginEventsPage.table().update(); + resultList = loginEventsPage.table().rows(); + + assertEquals(1, resultList.size()); + + resultList.get(0).findElement(By.xpath(".//td[text()='LOGIN']")); + resultList.get(0).findElement(By.xpath(".//td[text()='Client']/../td[text()='customer-portal']")); + resultList.get(0).findElement(By.xpath(".//td[text()='IP Address']/../td[text()='127.0.0.1' or text()='0:0:0:0:0:0:0:1']")); + resultList.get(0).findElement(By.xpath(".//td[text()='username']/../td[text()='bburke@redhat.com']")); + + loginEventsPage.table().reset(); + loginEventsPage.table().filterForm().addEventType("CODE_TO_TOKEN"); + loginEventsPage.table().update(); + resultList = loginEventsPage.table().rows(); + + assertEquals(1, resultList.size()); + resultList.get(0).findElement(By.xpath(".//td[text()='CODE_TO_TOKEN']")); + resultList.get(0).findElement(By.xpath(".//td[text()='Client']/../td[text()='customer-portal']")); + resultList.get(0).findElement(By.xpath(".//td[text()='IP Address']/../td[text()='127.0.0.1' or text()='0:0:0:0:0:0:0:1']")); + resultList.get(0).findElement(By.xpath(".//td[text()='refresh_token_type']/../td[text()='Refresh']")); + + configPage.navigateTo(); + configPage.form().clearLoginEvents(); + driver.findElement(By.xpath("//div[@class='modal-dialog']//button[text()='Delete']")).click(); + + String serverLogPath = null; + + if (System.getProperty("app.server").equals("wildfly") || System.getProperty("app.server").equals("eap6") || System.getProperty("app.server").equals("eap")) { + serverLogPath = System.getProperty("app.server.home") + "/standalone/log/server.log"; + } + + String appServerUrl; + if (Boolean.parseBoolean(System.getProperty("app.server.ssl.required"))) { + appServerUrl = "https://localhost:" + System.getProperty("app.server.https.port", "8543") + "/"; + } else { + appServerUrl = "http://localhost:" + System.getProperty("app.server.http.port", "8280") + "/"; + } + + if (serverLogPath != null) { + log.info("Checking app server log at: " + serverLogPath); + File serverLog = new File(serverLogPath); + String serverLogContent = FileUtils.readFileToString(serverLog); + UserRepresentation bburke = ApiUtil.findUserByUsername(testRealmResource(), "bburke@redhat.com"); + + Pattern pattern = Pattern.compile("User '" + bburke.getId() + "' invoking '" + appServerUrl + "customer-portal[^\\s]+' on client 'customer-portal'"); + Matcher matcher = pattern.matcher(serverLogContent); + + assertTrue(matcher.find()); + assertTrue(serverLogContent.contains("User '" + bburke.getId() + "' invoking '" + appServerUrl + "customer-db/' on client 'customer-db'")); + } else { + log.info("Checking app server log on app-server: \"" + System.getProperty("app.server") + "\" is not supported."); + } + } + } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractOfflineServletsAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractOfflineServletsAdapterTest.java index fa6d0a8aed..b8c10b6dac 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractOfflineServletsAdapterTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractOfflineServletsAdapterTest.java @@ -85,7 +85,7 @@ public abstract class AbstractOfflineServletsAdapterTest extends AbstractServlet String refreshTokenId = offlineTokenPage.getRefreshToken().getId(); setAdapterAndServerTimeOffset(9999); - + offlineTokenPage.navigateTo(); assertCurrentUrlStartsWith(offlineTokenPage); Assert.assertNotEquals(offlineTokenPage.getRefreshToken().getId(), refreshTokenId); Assert.assertNotEquals(offlineTokenPage.getAccessToken().getId(), accessTokenId); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSAMLServletsAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSAMLServletsAdapterTest.java index 2973caa1dc..5bdd305b6a 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSAMLServletsAdapterTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSAMLServletsAdapterTest.java @@ -327,8 +327,7 @@ public abstract class AbstractSAMLServletsAdapterTest extends AbstractServletsAd salesPostPassiveServletPage.navigateTo(); if (forbiddenIfNotAuthenticated) { - waitUntilElement(By.xpath("//body")).text().not().contains("principal="); - assertTrue(driver.getPageSource().contains("Forbidden") || driver.getPageSource().contains(FORBIDDEN_TEXT)); + assertOnForbiddenPage(); } else { waitUntilElement(By.xpath("//body")).text().contains("principal=null"); } @@ -407,9 +406,7 @@ public abstract class AbstractSAMLServletsAdapterTest extends AbstractServletsAd salesPostPassiveServletPage.navigateTo(); if (forbiddenIfNotAuthenticated) { - waitUntilElement(By.xpath("//body")).text().not().contains("principal="); - //Different 403 status page on EAP and Wildfly - assertTrue(driver.getPageSource().contains("Forbidden") || driver.getPageSource().contains(FORBIDDEN_TEXT)); + assertOnForbiddenPage(); } else { waitUntilElement(By.xpath("//body")).text().contains("principal=null"); } @@ -422,9 +419,7 @@ public abstract class AbstractSAMLServletsAdapterTest extends AbstractServletsAd salesPostPassiveServletPage.navigateTo(); if (forbiddenIfNotAuthenticated) { - waitUntilElement(By.xpath("//body")).text().not().contains("principal="); - //Different 403 status page on EAP and Wildfly - assertTrue(driver.getPageSource().contains("Forbidden") || driver.getPageSource().contains(FORBIDDEN_TEXT)); + assertOnForbiddenPage(); } else { waitUntilElement(By.xpath("//body")).text().contains("principal=null"); } @@ -724,4 +719,16 @@ public abstract class AbstractSAMLServletsAdapterTest extends AbstractServletsAd driver.navigate().to(employee2ServletPage.toString() + "/setCheckRoles?roles=" + roles); employee2ServletPage.logout(); } + + private void assertOnForbiddenPage() { + switch (System.getProperty("app.server")) { + case "eap6": + waitUntilElement(By.xpath("//body")).text().not().contains("principal="); + String source = driver.getPageSource(); + assertTrue(source.isEmpty() || source.contains("")); + break; + default: + waitUntilElement(By.xpath("//body")).text().contains(FORBIDDEN_TEXT); + } + } } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/PermissionsTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/PermissionsTest.java index 81de89463d..b6fca90ed3 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/PermissionsTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/PermissionsTest.java @@ -17,10 +17,12 @@ package org.keycloak.testsuite.admin; +import org.apache.bcel.generic.RETURN; import org.jboss.resteasy.plugins.providers.multipart.MultipartFormDataOutput; import org.junit.Rule; import org.junit.Test; import org.keycloak.admin.client.Keycloak; +import org.keycloak.admin.client.resource.AuthorizationResource; import org.keycloak.admin.client.resource.RealmResource; import org.keycloak.models.AdminRoles; import org.keycloak.models.Constants; @@ -45,6 +47,10 @@ import org.keycloak.representations.idm.RoleRepresentation; import org.keycloak.representations.idm.UserFederationMapperRepresentation; import org.keycloak.representations.idm.UserFederationProviderRepresentation; import org.keycloak.representations.idm.UserRepresentation; +import org.keycloak.representations.idm.authorization.PolicyRepresentation; +import org.keycloak.representations.idm.authorization.ResourceRepresentation; +import org.keycloak.representations.idm.authorization.ResourceServerRepresentation; +import org.keycloak.representations.idm.authorization.ScopeRepresentation; import org.keycloak.services.resources.admin.RealmAuth.Resource; import org.keycloak.testsuite.AbstractKeycloakTest; import org.keycloak.testsuite.Assert; @@ -68,6 +74,8 @@ import java.util.Map; import java.util.concurrent.atomic.AtomicReference; import static org.junit.Assert.*; +import static org.keycloak.services.resources.admin.RealmAuth.Resource.AUTHORIZATION; +import static org.keycloak.services.resources.admin.RealmAuth.Resource.CLIENT; /** * @author Stian Thorgersen @@ -770,6 +778,123 @@ public class PermissionsTest extends AbstractKeycloakTest { }, Resource.CLIENT, true); } + @Test + public void clientAuthorization() { + invoke(new InvocationWithResponse() { + public void invoke(RealmResource realm, AtomicReference response) { + realm.clients().create(ClientBuilder.create().clientId("foo-authz").build()); + org.keycloak.representations.idm.ClientRepresentation foo = realm.clients().findByClientId("foo-authz").get(0); + foo.setServiceAccountsEnabled(true); + foo.setAuthorizationServicesEnabled(true); + realm.clients().get(foo.getId()).update(foo); + } + }, CLIENT, true); + invoke(new Invocation() { + public void invoke(RealmResource realm) { + org.keycloak.representations.idm.ClientRepresentation foo = realm.clients().findByClientId("foo-authz").get(0); + realm.clients().get(foo.getId()).authorization().getSettings(); + } + }, AUTHORIZATION, false); + invoke(new Invocation() { + public void invoke(RealmResource realm) { + org.keycloak.representations.idm.ClientRepresentation foo = realm.clients().findByClientId("foo-authz").get(0); + AuthorizationResource authorization = realm.clients().get(foo.getId()).authorization(); + ResourceServerRepresentation settings = authorization.getSettings(); + authorization.update(settings); + } + }, AUTHORIZATION, true); + invoke(new Invocation() { + public void invoke(RealmResource realm) { + org.keycloak.representations.idm.ClientRepresentation foo = realm.clients().findByClientId("foo-authz").get(0); + AuthorizationResource authorization = realm.clients().get(foo.getId()).authorization(); + authorization.resources().resources(); + } + }, AUTHORIZATION, false); + invoke(new Invocation() { + public void invoke(RealmResource realm) { + org.keycloak.representations.idm.ClientRepresentation foo = realm.clients().findByClientId("foo-authz").get(0); + AuthorizationResource authorization = realm.clients().get(foo.getId()).authorization(); + authorization.scopes().scopes(); + } + }, AUTHORIZATION, false); + invoke(new Invocation() { + public void invoke(RealmResource realm) { + org.keycloak.representations.idm.ClientRepresentation foo = realm.clients().findByClientId("foo-authz").get(0); + AuthorizationResource authorization = realm.clients().get(foo.getId()).authorization(); + authorization.policies().policies(); + } + }, AUTHORIZATION, false); + invoke(new InvocationWithResponse() { + public void invoke(RealmResource realm, AtomicReference response) { + org.keycloak.representations.idm.ClientRepresentation foo = realm.clients().findByClientId("foo-authz").get(0); + AuthorizationResource authorization = realm.clients().get(foo.getId()).authorization(); + response.set(authorization.resources().create(new ResourceRepresentation("Test", Collections.emptySet()))); + } + }, AUTHORIZATION, true); + invoke(new InvocationWithResponse() { + public void invoke(RealmResource realm, AtomicReference response) { + org.keycloak.representations.idm.ClientRepresentation foo = realm.clients().findByClientId("foo-authz").get(0); + AuthorizationResource authorization = realm.clients().get(foo.getId()).authorization(); + response.set(authorization.scopes().create(new ScopeRepresentation("Test"))); + } + }, AUTHORIZATION, true); + invoke(new InvocationWithResponse() { + public void invoke(RealmResource realm, AtomicReference response) { + org.keycloak.representations.idm.ClientRepresentation foo = realm.clients().findByClientId("foo-authz").get(0); + AuthorizationResource authorization = realm.clients().get(foo.getId()).authorization(); + PolicyRepresentation representation = new PolicyRepresentation(); + representation.setName("Test PermissionsTest"); + representation.setType("js"); + HashMap config = new HashMap<>(); + config.put("code", ""); + representation.setConfig(config); + response.set(authorization.policies().create(representation)); + } + }, AUTHORIZATION, true); + invoke(new Invocation() { + public void invoke(RealmResource realm) { + org.keycloak.representations.idm.ClientRepresentation foo = realm.clients().findByClientId("foo-authz").get(0); + AuthorizationResource authorization = realm.clients().get(foo.getId()).authorization(); + authorization.resources().resource("nosuch").update(new ResourceRepresentation()); + } + }, AUTHORIZATION, true); + invoke(new Invocation() { + public void invoke(RealmResource realm) { + org.keycloak.representations.idm.ClientRepresentation foo = realm.clients().findByClientId("foo-authz").get(0); + AuthorizationResource authorization = realm.clients().get(foo.getId()).authorization(); + authorization.scopes().scope("nosuch").update(new ScopeRepresentation()); + } + }, AUTHORIZATION, true); + invoke(new Invocation() { + public void invoke(RealmResource realm) { + org.keycloak.representations.idm.ClientRepresentation foo = realm.clients().findByClientId("foo-authz").get(0); + AuthorizationResource authorization = realm.clients().get(foo.getId()).authorization(); + authorization.policies().policy("nosuch").update(new PolicyRepresentation()); + } + }, AUTHORIZATION, true); + invoke(new Invocation() { + public void invoke(RealmResource realm) { + org.keycloak.representations.idm.ClientRepresentation foo = realm.clients().findByClientId("foo-authz").get(0); + AuthorizationResource authorization = realm.clients().get(foo.getId()).authorization(); + authorization.resources().resource("nosuch").remove(); + } + }, AUTHORIZATION, true); + invoke(new Invocation() { + public void invoke(RealmResource realm) { + org.keycloak.representations.idm.ClientRepresentation foo = realm.clients().findByClientId("foo-authz").get(0); + AuthorizationResource authorization = realm.clients().get(foo.getId()).authorization(); + authorization.scopes().scope("nosuch").remove(); + } + }, AUTHORIZATION, true); + invoke(new Invocation() { + public void invoke(RealmResource realm) { + org.keycloak.representations.idm.ClientRepresentation foo = realm.clients().findByClientId("foo-authz").get(0); + AuthorizationResource authorization = realm.clients().get(foo.getId()).authorization(); + authorization.policies().policy("nosuch").remove(); + } + }, AUTHORIZATION, true); + } + @Test public void roles() { invoke(new Invocation() { @@ -1543,6 +1668,8 @@ public class PermissionsTest extends AbstractKeycloakTest { return AdminRoles.VIEW_EVENTS; case IDENTITY_PROVIDER: return AdminRoles.VIEW_IDENTITY_PROVIDERS; + case AUTHORIZATION: + return AdminRoles.VIEW_AUTHORIZATION; default: throw new RuntimeException("Unexpected resouce"); } @@ -1560,6 +1687,8 @@ public class PermissionsTest extends AbstractKeycloakTest { return AdminRoles.MANAGE_EVENTS; case IDENTITY_PROVIDER: return AdminRoles.MANAGE_IDENTITY_PROVIDERS; + case AUTHORIZATION: + return AdminRoles.MANAGE_AUTHORIZATION; default: throw new RuntimeException("Unexpected resouce"); } @@ -1577,6 +1706,8 @@ public class PermissionsTest extends AbstractKeycloakTest { return AdminRoles.VIEW_IDENTITY_PROVIDERS; case IDENTITY_PROVIDER: return AdminRoles.VIEW_REALM; + case AUTHORIZATION: + return AdminRoles.VIEW_IDENTITY_PROVIDERS; default: throw new RuntimeException("Unexpected resouce"); } @@ -1594,6 +1725,8 @@ public class PermissionsTest extends AbstractKeycloakTest { return AdminRoles.MANAGE_IDENTITY_PROVIDERS; case IDENTITY_PROVIDER: return AdminRoles.MANAGE_REALM; + case AUTHORIZATION: + return AdminRoles.MANAGE_IDENTITY_PROVIDERS; default: throw new RuntimeException("Unexpected resouce"); } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/AbstractAuthenticationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/AbstractAuthenticationTest.java index f115c49326..c32ba08618 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/AbstractAuthenticationTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/AbstractAuthenticationTest.java @@ -104,7 +104,7 @@ public abstract class AbstractAuthenticationTest extends AbstractKeycloakTest { } void compareExecution(AuthenticationExecutionExportRepresentation expected, AuthenticationExecutionExportRepresentation actual) { - Assert.assertEquals("Execution flowAlias - " + actual.getAuthenticator(), expected.getFlowAlias(), actual.getFlowAlias()); + Assert.assertEquals("Execution flowAlias - " + actual.getFlowAlias(), expected.getFlowAlias(), actual.getFlowAlias()); Assert.assertEquals("Execution authenticator - " + actual.getAuthenticator(), expected.getAuthenticator(), actual.getAuthenticator()); Assert.assertEquals("Execution userSetupAllowed - " + actual.getAuthenticator(), expected.isUserSetupAllowed(), actual.isUserSetupAllowed()); Assert.assertEquals("Execution authenticatorFlow - " + actual.getAuthenticator(), expected.isAutheticatorFlow(), actual.isAutheticatorFlow()); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/ExecutionTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/ExecutionTest.java index 42015edce0..d7caba46d5 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/ExecutionTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/ExecutionTest.java @@ -94,7 +94,7 @@ public class ExecutionTest extends AbstractAuthenticationTest { // we'll need auth-cookie later AuthenticationExecutionInfoRepresentation authCookieExec = findExecutionByProvider("auth-cookie", executionReps); - compareExecution(newExecInfo("Review Profile", "idp-review-profile", true, 0, 3, DISABLED, null, new String[]{REQUIRED, DISABLED}), exec); + compareExecution(newExecInfo("Review Profile", "idp-review-profile", true, 0, 4, DISABLED, null, new String[]{REQUIRED, DISABLED}), exec); // remove execution authMgmtResource.removeExecution(exec.getId()); @@ -164,7 +164,7 @@ public class ExecutionTest extends AbstractAuthenticationTest { // Note: there is no checking in addExecution if requirement is one of requirementChoices // Thus we can have OPTIONAL which is neither ALTERNATIVE, nor DISABLED - compareExecution(newExecInfo("Cookie", "auth-cookie", false, 0, 2, OPTIONAL, null, new String[]{ALTERNATIVE, DISABLED}), exec); + compareExecution(newExecInfo("Cookie", "auth-cookie", false, 0, 3, OPTIONAL, null, new String[]{ALTERNATIVE, DISABLED}), exec); } @Test diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/FlowTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/FlowTest.java index fc50bc6916..90f88745a9 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/FlowTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/FlowTest.java @@ -203,7 +203,7 @@ public class FlowTest extends AbstractAuthenticationTest { // adjust expected values before comparing browser.setAlias("Copy of browser"); browser.setBuiltIn(false); - browser.getAuthenticationExecutions().get(2).setFlowAlias("Copy of browser forms"); + browser.getAuthenticationExecutions().get(3).setFlowAlias("Copy of browser forms"); compareFlows(browser, copyOfBrowser); // get new flow directly and compare diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/InitialFlowsTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/InitialFlowsTest.java index 3640af5288..ee79c27213 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/InitialFlowsTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/InitialFlowsTest.java @@ -125,12 +125,14 @@ public class InitialFlowsTest extends AbstractAuthenticationTest { AuthenticationFlowRepresentation flow = newFlow("browser", "browser based authentication", "basic-flow", true, true); addExecExport(flow, null, false, "auth-cookie", false, null, ALTERNATIVE, 10); addExecExport(flow, null, false, "auth-spnego", false, null, DISABLED, 20); + addExecExport(flow, null, false, "identity-provider-redirector", false, null, ALTERNATIVE, 25); addExecExport(flow, "forms", false, null, true, null, ALTERNATIVE, 30); List execs = new LinkedList<>(); addExecInfo(execs, "Cookie", "auth-cookie", false, 0, 0, ALTERNATIVE, null, new String[]{ALTERNATIVE, DISABLED}); addExecInfo(execs, "Kerberos", "auth-spnego", false, 0, 1, DISABLED, null, new String[]{ALTERNATIVE, REQUIRED, DISABLED}); - addExecInfo(execs, "forms", null, false, 0, 2, ALTERNATIVE, true, new String[]{ALTERNATIVE, REQUIRED, DISABLED}); + addExecInfo(execs, "Identity Provider Redirector", "identity-provider-redirector", true, 0, 2, ALTERNATIVE, null, new String[]{ALTERNATIVE, DISABLED}); + addExecInfo(execs, "forms", null, false, 0, 3, ALTERNATIVE, true, new String[]{ALTERNATIVE, REQUIRED, DISABLED}); addExecInfo(execs, "Username Password Form", "auth-username-password-form", false, 1, 0, REQUIRED, null, new String[]{REQUIRED}); addExecInfo(execs, "OTP Form", "auth-otp-form", false, 1, 1, OPTIONAL, null, new String[]{REQUIRED, OPTIONAL, DISABLED}); expected.add(new FlowExecutions(flow, execs)); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/ProvidersTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/ProvidersTest.java index 858e06ef45..4e07e6fbcc 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/ProvidersTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/ProvidersTest.java @@ -146,6 +146,7 @@ public class ProvidersTest extends AbstractAuthenticationTest { addProviderInfo(result, "direct-grant-validate-username", "Username Validation", "Validates the username supplied as a 'username' form parameter in direct grant request"); addProviderInfo(result, "http-basic-authenticator", "HTTP Basic Authentication", "Validates username and password from Authorization HTTP header"); + addProviderInfo(result, "identity-provider-redirector", "Identity Provider Redirector", "Redirects to default Identity Provider or Identity Provider specified with kc_idp_hint query parameter"); addProviderInfo(result, "idp-confirm-link", "Confirm link existing account", "Show the form where user confirms if he wants " + "to link identity provider with existing account or rather edit user profile data retrieved from identity provider to avoid conflict"); addProviderInfo(result, "idp-create-user-if-unique", "Create User If Unique", "Detect if there is existing Keycloak account " + diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/InstallationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/InstallationTest.java index fb03891e15..c7339ed12d 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/InstallationTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/InstallationTest.java @@ -21,11 +21,11 @@ import org.junit.After; import org.junit.Before; import org.junit.Test; import org.keycloak.admin.client.resource.ClientResource; -import org.keycloak.events.admin.OperationType; import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.testsuite.arquillian.AuthServerTestEnricher; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertFalse; /** * Test getting the installation/configuration files for OIDC and SAML. @@ -81,7 +81,7 @@ public class InstallationTest extends AbstractClientTest { private void assertOidcInstallationConfig(String config) { RealmRepresentation realmRep = realmRep(); assertTrue(config.contains(realmRep.getId())); - assertTrue(config.contains(realmRep.getPublicKey())); + assertFalse(config.contains(realmRep.getPublicKey())); assertTrue(config.contains(authServerUrl())); } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/authorization/AbstractAuthorizationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/authorization/AbstractAuthorizationTest.java index c4979e0f67..2c3aac71e0 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/authorization/AbstractAuthorizationTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/authorization/AbstractAuthorizationTest.java @@ -19,11 +19,13 @@ package org.keycloak.testsuite.admin.client.authorization; import org.junit.After; import org.junit.Before; +import org.junit.BeforeClass; import org.keycloak.admin.client.resource.ClientResource; import org.keycloak.admin.client.resource.ResourceScopeResource; import org.keycloak.admin.client.resource.ResourceScopesResource; import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.authorization.ScopeRepresentation; +import org.keycloak.testsuite.ProfileAssume; import org.keycloak.testsuite.admin.client.AbstractClientTest; import javax.ws.rs.core.Response; @@ -38,6 +40,11 @@ public abstract class AbstractAuthorizationTest extends AbstractClientTest { protected static final String RESOURCE_SERVER_CLIENT_ID = "test-resource-server"; + @BeforeClass + public static void enabled() { + ProfileAssume.assumePreview(); + } + @Before public void onBeforeAuthzTests() { createOidcClient(RESOURCE_SERVER_CLIENT_ID); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/authorization/AuthorizationDisabledInPreviewTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/authorization/AuthorizationDisabledInPreviewTest.java new file mode 100644 index 0000000000..072aa5f630 --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/authorization/AuthorizationDisabledInPreviewTest.java @@ -0,0 +1,50 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.keycloak.testsuite.admin.client.authorization; + +import org.junit.BeforeClass; +import org.junit.Test; +import org.keycloak.testsuite.ProfileAssume; +import org.keycloak.testsuite.admin.client.AbstractClientTest; + +import javax.ws.rs.ServerErrorException; +import javax.ws.rs.core.Response; + +import static org.junit.Assert.assertEquals; + +/** + * @author Stian Thorgersen + */ +public class AuthorizationDisabledInPreviewTest extends AbstractClientTest { + + @BeforeClass + public static void enabled() { + ProfileAssume.assumePreviewDisabled(); + } + + @Test + public void testAuthzServicesRemoved() { + String id = testRealmResource().clients().findAll().get(0).getId(); + try { + testRealmResource().clients().get(id).authorization().getSettings(); + } catch (ServerErrorException e) { + assertEquals(Response.Status.NOT_IMPLEMENTED.getStatusCode(), e.getResponse().getStatus()); + } + } + +} diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/realm/RealmTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/realm/RealmTest.java index f08f79059e..926d31d5ad 100755 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/realm/RealmTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/realm/RealmTest.java @@ -57,14 +57,10 @@ import java.util.LinkedList; import java.util.List; import java.util.HashSet; import java.util.Arrays; +import java.util.HashMap; import java.util.Map; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; +import static org.junit.Assert.*; /** * @author Stian Thorgersen @@ -237,6 +233,7 @@ public class RealmTest extends AbstractAdminTest { assertEquals(Boolean.FALSE, rep.isRegistrationAllowed()); assertEquals(Boolean.FALSE, rep.isRegistrationEmailAsUsername()); assertEquals(Boolean.FALSE, rep.isEditUsernameAllowed()); + } @Test @@ -266,6 +263,45 @@ public class RealmTest extends AbstractAdminTest { assertEquals(2, rep.getSupportedLocales().size()); } + @Test + public void updateRealmAttributes() { + // first change + RealmRepresentation rep = new RealmRepresentation(); + rep.setAttributes(new HashMap<>()); + rep.getAttributes().put("foo1", "bar1"); + rep.getAttributes().put("foo2", "bar2"); + + rep.setBruteForceProtected(true); + rep.setDisplayName("dn1"); + + realm.update(rep); + assertAdminEvents.assertEvent(realmId, OperationType.UPDATE, Matchers.nullValue(String.class), rep, ResourceType.REALM); + + rep = realm.toRepresentation(); + + assertEquals("bar1", rep.getAttributes().get("foo1")); + assertEquals("bar2", rep.getAttributes().get("foo2")); + assertTrue(rep.isBruteForceProtected()); + assertEquals("dn1", rep.getDisplayName()); + + // second change + rep.setBruteForceProtected(false); + rep.setDisplayName("dn2"); + rep.getAttributes().put("foo1", "bar11"); + rep.getAttributes().remove("foo2"); + + realm.update(rep); + assertAdminEvents.assertEvent(realmId, OperationType.UPDATE, Matchers.nullValue(String.class), rep, ResourceType.REALM); + + rep = realm.toRepresentation(); + + assertFalse(rep.isBruteForceProtected()); + assertEquals("dn2", rep.getDisplayName()); + + assertEquals("bar11", rep.getAttributes().get("foo1")); + assertFalse(rep.getAttributes().containsKey("foo2")); + } + @Test public void getRealmRepresentation() { RealmRepresentation rep = realm.toRepresentation(); @@ -393,6 +429,13 @@ public class RealmTest extends AbstractAdminTest { assertEquals(realm.getBrowserSecurityHeaders(), storedRealm.getBrowserSecurityHeaders()); } + if (realm.getAttributes() != null) { + HashMap attributes = new HashMap<>(); + attributes.putAll(storedRealm.getAttributes()); + attributes.entrySet().retainAll(realm.getAttributes().entrySet()); + assertEquals(realm.getAttributes(), attributes); + } + } @Test @@ -515,6 +558,26 @@ public class RealmTest extends AbstractAdminTest { assertEquals(certificate, realm.toRepresentation().getCertificate()); } + @Test + public void rotateRealmKeys() { + RealmRepresentation realmRep = realm.toRepresentation(); + String publicKey = realmRep.getPublicKey(); + String cert = realmRep.getCertificate(); + assertNotNull(publicKey); + assertNotNull(cert); + + RealmRepresentation newRealmRep = new RealmRepresentation(); + newRealmRep.setRealm(REALM_NAME); + newRealmRep.setPublicKey("GENERATE"); + realm.update(newRealmRep); + + realmRep = realm.toRepresentation(); + assertNotNull(realmRep.getPublicKey()); + assertNotNull(realmRep.getCertificate()); + assertNotEquals(publicKey, realmRep.getPublicKey()); + assertNotEquals(cert, realmRep.getCertificate()); + } + @Test public void clearRealmCache() { RealmRepresentation realmRep = realm.toRepresentation(); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/AdapterInstallationConfigTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/AdapterInstallationConfigTest.java index 0a4fe4bea7..818a84a992 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/AdapterInstallationConfigTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/AdapterInstallationConfigTest.java @@ -35,14 +35,11 @@ public class AdapterInstallationConfigTest extends AbstractClientRegistrationTes private ClientRepresentation client; private ClientRepresentation client2; private ClientRepresentation clientPublic; - private String publicKey; @Before public void before() throws Exception { super.before(); - publicKey = adminClient.realm(REALM_NAME).toRepresentation().getPublicKey(); - client = new ClientRepresentation(); client.setEnabled(true); client.setClientId("RegistrationAccessTokenTest"); @@ -92,7 +89,6 @@ public class AdapterInstallationConfigTest extends AbstractClientRegistrationTes assertEquals(1, config.getCredentials().size()); assertEquals(client.getSecret(), config.getCredentials().get("secret")); - assertEquals(publicKey, config.getRealmKey()); assertEquals(client.getClientId(), config.getResource()); assertEquals(SslRequired.EXTERNAL.name().toLowerCase(), config.getSslRequired()); } @@ -132,7 +128,6 @@ public class AdapterInstallationConfigTest extends AbstractClientRegistrationTes assertEquals(0, config.getCredentials().size()); - assertEquals(publicKey, config.getRealmKey()); assertEquals(clientPublic.getClientId(), config.getResource()); assertEquals(SslRequired.EXTERNAL.name().toLowerCase(), config.getSslRequired()); } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCClientRegistrationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCClientRegistrationTest.java index 608d7a73c5..d13bfa1fb7 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCClientRegistrationTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCClientRegistrationTest.java @@ -50,12 +50,12 @@ import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.oidc.OIDCClientRepresentation; import org.keycloak.testsuite.Assert; +import org.keycloak.testsuite.client.resources.TestApplicationResourceUrls; +import org.keycloak.testsuite.client.resources.TestOIDCEndpointsApplicationResource; +import org.keycloak.testsuite.rest.resource.TestingOIDCEndpointsApplicationResource; import org.keycloak.testsuite.util.OAuthClient; import java.security.PrivateKey; -import java.util.Arrays; -import java.util.Collections; -import java.util.LinkedList; -import java.util.List; +import java.util.*; import javax.ws.rs.core.Response; import javax.ws.rs.core.UriBuilder; @@ -236,8 +236,11 @@ public class OIDCClientRegistrationTest extends AbstractClientRegistrationTest { clientRep.setGrantTypes(Collections.singletonList(OAuth2Constants.CLIENT_CREDENTIALS)); clientRep.setTokenEndpointAuthMethod(OIDCLoginProtocol.PRIVATE_KEY_JWT); - // Corresponds to PRIVATE_KEY - JSONWebKeySet keySet = loadJson(getClass().getResourceAsStream("/clientreg-test/jwks.json"), JSONWebKeySet.class); + // Generate keys for client + TestOIDCEndpointsApplicationResource oidcClientEndpointsResource = testingClient.testApp().oidcClientEndpoints(); + Map generatedKeys = oidcClientEndpointsResource.generateKeys(); + + JSONWebKeySet keySet = oidcClientEndpointsResource.getJwks(); clientRep.setJwks(keySet); OIDCClientRepresentation response = reg.oidc().create(clientRep); @@ -246,7 +249,7 @@ public class OIDCClientRegistrationTest extends AbstractClientRegistrationTest { Assert.assertNull(response.getClientSecretExpiresAt()); // Tries to authenticate client with privateKey JWT - String signedJwt = getClientSignedJWT(response.getClientId()); + String signedJwt = getClientSignedJWT(response.getClientId(), generatedKeys.get(TestingOIDCEndpointsApplicationResource.PRIVATE_KEY)); OAuthClient.AccessTokenResponse accessTokenResponse = doClientCredentialsGrantRequest(signedJwt); Assert.assertEquals(200, accessTokenResponse.getStatusCode()); AccessToken accessToken = oauth.verifyToken(accessTokenResponse.getAccessToken()); @@ -260,8 +263,11 @@ public class OIDCClientRegistrationTest extends AbstractClientRegistrationTest { clientRep.setGrantTypes(Collections.singletonList(OAuth2Constants.CLIENT_CREDENTIALS)); clientRep.setTokenEndpointAuthMethod(OIDCLoginProtocol.PRIVATE_KEY_JWT); - // Use the realmKey for client authentication too - clientRep.setJwksUri(oauth.getCertsUrl(REALM_NAME)); + // Generate keys for client + TestOIDCEndpointsApplicationResource oidcClientEndpointsResource = testingClient.testApp().oidcClientEndpoints(); + Map generatedKeys = oidcClientEndpointsResource.generateKeys(); + + clientRep.setJwksUri(TestApplicationResourceUrls.clientJwksUri()); OIDCClientRepresentation response = reg.oidc().create(clientRep); Assert.assertEquals(OIDCLoginProtocol.PRIVATE_KEY_JWT, response.getTokenEndpointAuthMethod()); @@ -269,35 +275,169 @@ public class OIDCClientRegistrationTest extends AbstractClientRegistrationTest { Assert.assertNull(response.getClientSecretExpiresAt()); // Tries to authenticate client with privateKey JWT - String signedJwt = getClientSignedJWT(response.getClientId()); + String signedJwt = getClientSignedJWT(response.getClientId(), generatedKeys.get(TestingOIDCEndpointsApplicationResource.PRIVATE_KEY)); OAuthClient.AccessTokenResponse accessTokenResponse = doClientCredentialsGrantRequest(signedJwt); Assert.assertEquals(200, accessTokenResponse.getStatusCode()); AccessToken accessToken = oauth.verifyToken(accessTokenResponse.getAccessToken()); Assert.assertEquals(response.getClientId(), accessToken.getAudience()[0]); } + @Test + public void createPairwiseClient() throws Exception { + OIDCClientRepresentation clientRep = createRep(); + clientRep.setSubjectType("pairwise"); + + OIDCClientRepresentation response = reg.oidc().create(clientRep); + Assert.assertEquals("pairwise", response.getSubjectType()); + } + + @Test + public void updateClientToPairwise() throws Exception { + OIDCClientRepresentation response = create(); + Assert.assertEquals("public", response.getSubjectType()); + + reg.auth(Auth.token(response)); + response.setSubjectType("pairwise"); + OIDCClientRepresentation updated = reg.oidc().update(response); + + Assert.assertEquals("pairwise", updated.getSubjectType()); + } + + @Test + public void updateSectorIdentifierUri() throws Exception { + OIDCClientRepresentation clientRep = createRep(); + clientRep.setSubjectType("pairwise"); + OIDCClientRepresentation response = reg.oidc().create(clientRep); + Assert.assertEquals("pairwise", response.getSubjectType()); + Assert.assertNull(response.getSectorIdentifierUri()); + + reg.auth(Auth.token(response)); + + // Push redirect uris to the sector identifier URI + List sectorRedirects = new ArrayList<>(); + sectorRedirects.addAll(response.getRedirectUris()); + TestOIDCEndpointsApplicationResource oidcClientEndpointsResource = testingClient.testApp().oidcClientEndpoints(); + oidcClientEndpointsResource.setSectorIdentifierRedirectUris(sectorRedirects); + + response.setSectorIdentifierUri(TestApplicationResourceUrls.pairwiseSectorIdentifierUri()); + + OIDCClientRepresentation updated = reg.oidc().update(response); + + Assert.assertEquals("pairwise", updated.getSubjectType()); + Assert.assertEquals(TestApplicationResourceUrls.pairwiseSectorIdentifierUri(), updated.getSectorIdentifierUri()); + + } + + @Test + public void createPairwiseClientWithSectorIdentifierURI() throws Exception { + OIDCClientRepresentation clientRep = createRep(); + + // Push redirect uris to the sector identifier URI + List sectorRedirects = new ArrayList<>(); + sectorRedirects.addAll(clientRep.getRedirectUris()); + TestOIDCEndpointsApplicationResource oidcClientEndpointsResource = testingClient.testApp().oidcClientEndpoints(); + oidcClientEndpointsResource.setSectorIdentifierRedirectUris(sectorRedirects); + + clientRep.setSubjectType("pairwise"); + clientRep.setSectorIdentifierUri(TestApplicationResourceUrls.pairwiseSectorIdentifierUri()); + + OIDCClientRepresentation response = reg.oidc().create(clientRep); + Assert.assertEquals("pairwise", response.getSubjectType()); + Assert.assertEquals(TestApplicationResourceUrls.pairwiseSectorIdentifierUri(), response.getSectorIdentifierUri()); + } + + @Test + public void createPairwiseClientWithRedirectsToMultipleHostsWithoutSectorIdentifierURI() throws Exception { + OIDCClientRepresentation clientRep = createRep(); + + List redirects = new ArrayList<>(); + redirects.add("http://redirect1"); + redirects.add("http://redirect2"); + + clientRep.setSubjectType("pairwise"); + clientRep.setRedirectUris(redirects); + + assertCreateFail(clientRep, 400, "Without a configured Sector Identifier URI, client redirect URIs must not contain multiple host components."); + } + + @Test + public void createPairwiseClientWithRedirectsToMultipleHosts() throws Exception { + OIDCClientRepresentation clientRep = createRep(); + + // Push redirect URIs to the sector identifier URI + List redirects = new ArrayList<>(); + redirects.add("http://redirect1"); + redirects.add("http://redirect2"); + TestOIDCEndpointsApplicationResource oidcClientEndpointsResource = testingClient.testApp().oidcClientEndpoints(); + oidcClientEndpointsResource.setSectorIdentifierRedirectUris(redirects); + + clientRep.setSubjectType("pairwise"); + clientRep.setSectorIdentifierUri(TestApplicationResourceUrls.pairwiseSectorIdentifierUri()); + clientRep.setRedirectUris(redirects); + + OIDCClientRepresentation response = reg.oidc().create(clientRep); + Assert.assertEquals("pairwise", response.getSubjectType()); + Assert.assertEquals(TestApplicationResourceUrls.pairwiseSectorIdentifierUri(), response.getSectorIdentifierUri()); + Assert.assertNames(response.getRedirectUris(), "http://redirect1", "http://redirect2"); + } + + @Test + public void createPairwiseClientWithSectorIdentifierURIContainingMismatchedRedirects() throws Exception { + OIDCClientRepresentation clientRep = createRep(); + + // Push redirect uris to the sector identifier URI + List sectorRedirects = new ArrayList<>(); + sectorRedirects.add("http://someotherredirect"); + TestOIDCEndpointsApplicationResource oidcClientEndpointsResource = testingClient.testApp().oidcClientEndpoints(); + oidcClientEndpointsResource.setSectorIdentifierRedirectUris(sectorRedirects); + + clientRep.setSubjectType("pairwise"); + clientRep.setSectorIdentifierUri(TestApplicationResourceUrls.pairwiseSectorIdentifierUri()); + + assertCreateFail(clientRep, 400, "Client redirect URIs does not match redirect URIs fetched from the Sector Identifier URI."); + } + + @Test + public void createPairwiseClientWithInvalidSectorIdentifierURI() throws Exception { + OIDCClientRepresentation clientRep = createRep(); + clientRep.setSubjectType("pairwise"); + clientRep.setSectorIdentifierUri("malformed"); + assertCreateFail(clientRep, 400, "Invalid Sector Identifier URI."); + } + + @Test + public void createPairwiseClientWithUnreachableSectorIdentifierURI() throws Exception { + OIDCClientRepresentation clientRep = createRep(); + clientRep.setSubjectType("pairwise"); + clientRep.setSectorIdentifierUri("http://localhost/dummy"); + assertCreateFail(clientRep, 400, "Failed to get redirect URIs from the Sector Identifier URI."); + } + @Test public void testSignaturesRequired() throws Exception { OIDCClientRepresentation clientRep = createRep(); clientRep.setUserinfoSignedResponseAlg(Algorithm.RS256.toString()); + clientRep.setRequestObjectSigningAlg(Algorithm.RS256.toString()); OIDCClientRepresentation response = reg.oidc().create(clientRep); Assert.assertEquals(Algorithm.RS256.toString(), response.getUserinfoSignedResponseAlg()); + Assert.assertEquals(Algorithm.RS256.toString(), response.getRequestObjectSigningAlg()); Assert.assertNotNull(response.getClientSecret()); // Test Keycloak representation ClientRepresentation kcClient = getClient(response.getClientId()); OIDCAdvancedConfigWrapper config = OIDCAdvancedConfigWrapper.fromClientRepresentation(kcClient); Assert.assertEquals(config.getUserInfoSignedResponseAlg(), Algorithm.RS256); + Assert.assertEquals(config.getRequestObjectSignatureAlg(), Algorithm.RS256); } // Client auth with signedJWT - helper methods - private String getClientSignedJWT(String clientId) { + private String getClientSignedJWT(String clientId, String privateKeyPem) { String realmInfoUrl = KeycloakUriBuilder.fromUri(getAuthServerRoot()).path(ServiceUrlConstants.REALM_INFO_PATH).build(REALM_NAME).toString(); - PrivateKey privateKey = KeycloakModelUtils.getPrivateKey(PRIVATE_KEY); + PrivateKey privateKey = KeycloakModelUtils.getPrivateKey(privateKeyPem); // Use token-endpoint as audience as OIDC conformance testsuite is using it too. JWTClientCredentialsProvider jwtProvider = new JWTClientCredentialsProvider() { diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/exportimport/ExportImportTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/exportimport/ExportImportTest.java index 6abaf97456..43c6fa9bb9 100755 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/exportimport/ExportImportTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/exportimport/ExportImportTest.java @@ -75,11 +75,11 @@ public class ExportImportTest extends AbstractExportImportTest { @Test public void testDirFullExportImport() throws Throwable { - testingClient.testing().setProvider(DirExportProviderFactory.PROVIDER_ID); - String targetDirPath = testingClient.testing().getExportImportTestDirectory()+ File.separator + "dirExport"; + testingClient.testing().exportImport().setProvider(DirExportProviderFactory.PROVIDER_ID); + String targetDirPath = testingClient.testing().exportImport().getExportImportTestDirectory()+ File.separator + "dirExport"; DirExportProvider.recursiveDeleteDir(new File(targetDirPath)); - testingClient.testing().setDir(targetDirPath); - testingClient.testing().setUsersPerFile(ExportImportConfig.DEFAULT_USERS_PER_FILE); + testingClient.testing().exportImport().setDir(targetDirPath); + testingClient.testing().exportImport().setUsersPerFile(ExportImportConfig.DEFAULT_USERS_PER_FILE); testFullExportImport(); @@ -89,11 +89,13 @@ public class ExportImportTest extends AbstractExportImportTest { @Test public void testDirRealmExportImport() throws Throwable { - testingClient.testing().setProvider(DirExportProviderFactory.PROVIDER_ID); - String targetDirPath = testingClient.testing().getExportImportTestDirectory() + File.separator + "dirRealmExport"; + testingClient.testing() + .exportImport() + .setProvider(DirExportProviderFactory.PROVIDER_ID); + String targetDirPath = testingClient.testing().exportImport().getExportImportTestDirectory() + File.separator + "dirRealmExport"; DirExportProvider.recursiveDeleteDir(new File(targetDirPath)); - testingClient.testing().setDir(targetDirPath); - testingClient.testing().setUsersPerFile(3); + testingClient.testing().exportImport().setDir(targetDirPath); + testingClient.testing().exportImport().setUsersPerFile(3); testRealmExportImport(); @@ -104,18 +106,18 @@ public class ExportImportTest extends AbstractExportImportTest { @Test public void testSingleFileFullExportImport() throws Throwable { - testingClient.testing().setProvider(SingleFileExportProviderFactory.PROVIDER_ID); - String targetFilePath = testingClient.testing().getExportImportTestDirectory() + File.separator + "singleFile-full.json"; - testingClient.testing().setFile(targetFilePath); + testingClient.testing().exportImport().setProvider(SingleFileExportProviderFactory.PROVIDER_ID); + String targetFilePath = testingClient.testing().exportImport().getExportImportTestDirectory() + File.separator + "singleFile-full.json"; + testingClient.testing().exportImport().setFile(targetFilePath); testFullExportImport(); } @Test public void testSingleFileRealmExportImport() throws Throwable { - testingClient.testing().setProvider(SingleFileExportProviderFactory.PROVIDER_ID); - String targetFilePath = testingClient.testing().getExportImportTestDirectory() + File.separator + "singleFile-realm.json"; - testingClient.testing().setFile(targetFilePath); + testingClient.testing().exportImport().setProvider(SingleFileExportProviderFactory.PROVIDER_ID); + String targetFilePath = testingClient.testing().exportImport().getExportImportTestDirectory() + File.separator + "singleFile-realm.json"; + testingClient.testing().exportImport().setFile(targetFilePath); testRealmExportImport(); } @@ -126,14 +128,14 @@ public class ExportImportTest extends AbstractExportImportTest { removeRealm("test-realm"); // Set the realm, which doesn't have builtin clients/roles inside JSON - testingClient.testing().setProvider(SingleFileExportProviderFactory.PROVIDER_ID); + testingClient.testing().exportImport().setProvider(SingleFileExportProviderFactory.PROVIDER_ID); URL url = ExportImportTest.class.getResource("/model/testrealm.json"); String targetFilePath = new File(url.getFile()).getAbsolutePath(); - testingClient.testing().setFile(targetFilePath); + testingClient.testing().exportImport().setFile(targetFilePath); - testingClient.testing().setAction(ExportImportConfig.ACTION_IMPORT); + testingClient.testing().exportImport().setAction(ExportImportConfig.ACTION_IMPORT); - testingClient.testing().runImport(); + testingClient.testing().exportImport().runImport(); RealmResource testRealmRealm = adminClient.realm("test-realm"); @@ -158,14 +160,14 @@ public class ExportImportTest extends AbstractExportImportTest { realm.components().add(component); - testingClient.testing().setProvider(SingleFileExportProviderFactory.PROVIDER_ID); + testingClient.testing().exportImport().setProvider(SingleFileExportProviderFactory.PROVIDER_ID); - String targetFilePath = testingClient.testing().getExportImportTestDirectory() + File.separator + "singleFile-realm.json"; - testingClient.testing().setFile(targetFilePath); - testingClient.testing().setAction(ExportImportConfig.ACTION_EXPORT); - testingClient.testing().setRealmName("component-realm"); + String targetFilePath = testingClient.testing().exportImport().getExportImportTestDirectory() + File.separator + "singleFile-realm.json"; + testingClient.testing().exportImport().setFile(targetFilePath); + testingClient.testing().exportImport().setAction(ExportImportConfig.ACTION_EXPORT); + testingClient.testing().exportImport().setRealmName("component-realm"); - testingClient.testing().runExport(); + testingClient.testing().exportImport().runExport(); // Delete some realm (and some data in admin realm) adminClient.realm("component-realm").remove(); @@ -173,9 +175,9 @@ public class ExportImportTest extends AbstractExportImportTest { Assert.assertEquals(3, adminClient.realms().findAll().size()); // Configure import - testingClient.testing().setAction(ExportImportConfig.ACTION_IMPORT); + testingClient.testing().exportImport().setAction(ExportImportConfig.ACTION_IMPORT); - testingClient.testing().runImport(); + testingClient.testing().exportImport().runImport(); realmRep = realm.toRepresentation(); @@ -203,10 +205,10 @@ public class ExportImportTest extends AbstractExportImportTest { } private void testFullExportImport() throws LifecycleException { - testingClient.testing().setAction(ExportImportConfig.ACTION_EXPORT); - testingClient.testing().setRealmName(""); + testingClient.testing().exportImport().setAction(ExportImportConfig.ACTION_EXPORT); + testingClient.testing().exportImport().setRealmName(""); - testingClient.testing().runExport(); + testingClient.testing().exportImport().runExport(); removeRealm("test"); removeRealm("test-realm"); @@ -218,9 +220,9 @@ public class ExportImportTest extends AbstractExportImportTest { assertNotAuthenticated("test", "user3", "password"); // Configure import - testingClient.testing().setAction(ExportImportConfig.ACTION_IMPORT); + testingClient.testing().exportImport().setAction(ExportImportConfig.ACTION_IMPORT); - testingClient.testing().runImport(); + testingClient.testing().exportImport().runImport(); // Ensure data are imported back Assert.assertEquals(3, adminClient.realms().findAll().size()); @@ -232,10 +234,10 @@ public class ExportImportTest extends AbstractExportImportTest { } private void testRealmExportImport() throws LifecycleException { - testingClient.testing().setAction(ExportImportConfig.ACTION_EXPORT); - testingClient.testing().setRealmName("test"); + testingClient.testing().exportImport().setAction(ExportImportConfig.ACTION_EXPORT); + testingClient.testing().exportImport().setRealmName("test"); - testingClient.testing().runExport(); + testingClient.testing().exportImport().runExport(); // Delete some realm (and some data in admin realm) adminClient.realm("test").remove(); @@ -248,9 +250,9 @@ public class ExportImportTest extends AbstractExportImportTest { assertNotAuthenticated("test", "user3", "password"); // Configure import - testingClient.testing().setAction(ExportImportConfig.ACTION_IMPORT); + testingClient.testing().exportImport().setAction(ExportImportConfig.ACTION_IMPORT); - testingClient.testing().runImport(); + testingClient.testing().exportImport().runImport(); // Ensure data are imported back, but just for "test" realm Assert.assertEquals(3, adminClient.realms().findAll().size()); @@ -273,27 +275,4 @@ public class ExportImportTest extends AbstractExportImportTest { Assert.assertEquals(expectedResult, testingClient.testing().validCredentials(realmName, username, password)); } - private static String getExportImportTestDirectory() { - String dirPath = null; - String relativeDirExportImportPath = "testsuite" + File.separator + - "integration-arquillian" + File.separator + - "tests" + File.separator + - "base" + File.separator + - "target" + File.separator + - "export-import"; - - if (System.getProperties().containsKey("maven.home")) { - dirPath = System.getProperty("user.dir").replaceFirst("testsuite.integration.*", Matcher.quoteReplacement(relativeDirExportImportPath)); - } else { - for (String c : System.getProperty("java.class.path").split(File.pathSeparator)) { - if (c.contains(File.separator + "testsuite" + File.separator + "integration-arquillian" + File.separator)) { - dirPath = c.replaceFirst("testsuite.integration-arquillian.*", Matcher.quoteReplacement(relativeDirExportImportPath)); - } - } - } - - String absolutePath = new File(dirPath).getAbsolutePath(); - return absolutePath; - } - } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/LoginTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/LoginTest.java index accbd37e33..5bef3d2472 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/LoginTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/LoginTest.java @@ -480,6 +480,40 @@ public class LoginTest extends TestRealmKeycloakTest { setRememberMe(false); } } + + @Test + // KEYCLOAK-3181 + public void loginWithEmailUserAndRememberMe() { + setRememberMe(true); + + try { + loginPage.open(); + loginPage.setRememberMe(true); + assertTrue(loginPage.isRememberMeChecked()); + loginPage.login("login@test.com", "password"); + + Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType()); + Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE)); + EventRepresentation loginEvent = events.expectLogin().user(userId) + .detail(Details.USERNAME, "login@test.com") + .detail(Details.REMEMBER_ME, "true") + .assertEvent(); + String sessionId = loginEvent.getSessionId(); + + // Expire session + testingClient.testing().removeUserSession("test", sessionId); + + // Assert rememberMe checked and username/email prefilled + loginPage.open(); + assertTrue(loginPage.isRememberMeChecked()); + + Assert.assertEquals("login@test.com", loginPage.getUsername()); + + loginPage.setRememberMe(false); + } finally { + setRememberMe(false); + } + } // KEYCLOAK-1037 @Test diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/MigrationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/MigrationTest.java index 78d4faa2c7..1864324c78 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/MigrationTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/MigrationTest.java @@ -17,69 +17,52 @@ package org.keycloak.testsuite.migration; import java.util.List; -import static org.junit.Assert.*; -import org.junit.Ignore; +import org.junit.After; +import org.junit.Before; import org.junit.Test; -import org.keycloak.admin.client.resource.ClientsResource; import org.keycloak.admin.client.resource.RealmResource; -import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.RealmRepresentation; -import org.keycloak.representations.idm.RoleRepresentation; import org.keycloak.testsuite.AbstractKeycloakTest; +import org.keycloak.testsuite.Assert; import org.keycloak.testsuite.arquillian.migration.Migration; /** * @author Vlastislav Ramik */ public class MigrationTest extends AbstractKeycloakTest { - + + private RealmResource realmResource; + private RealmRepresentation realmRep; + @Override public void addTestRealms(List testRealms) { log.info("Adding no test realms for migration test. Test realm should be migrated from previous vesrion."); } - @Test - @Migration(versionFrom = "1.6.1.Final") - public void migration16Test() { - RealmResource realmResource = adminClient.realms().realm("Migration"); - RealmRepresentation realmRep = realmResource.toRepresentation(); - assertEquals("Migration", realmRep.getRealm()); - - List realmRoles = realmResource.roles().list(); - assertEquals(1, realmRoles.size()); - assertEquals("offline_access", realmRoles.get(0).getName()); - - for (ClientRepresentation client : realmResource.clients().findAll()) { - final String clientId = client.getClientId(); - switch (clientId) { - case "realm-management": - assertEquals(13, realmResource.clients().get(client.getId()).roles().list().size()); - break; - case "security-admin-console": - assertEquals(0, realmResource.clients().get(client.getId()).roles().list().size()); - break; - case "broker": - assertEquals(1, realmResource.clients().get(client.getId()).roles().list().size()); - break; - case "account": - assertEquals(2, realmResource.clients().get(client.getId()).roles().list().size()); - break; - default: - fail("Migrated realm contains unexpected client " + clientId); - break; - } - } + @Before + public void beforeMigrationTest() { + realmResource = adminClient.realms().realm("Migration"); + realmRep = realmResource.toRepresentation(); } @Test - @Migration(versionFrom = "1.5.1.Final") - @Ignore - public void migration15Test() { - for (RealmRepresentation realm : adminClient.realms().findAll()) { - System.out.println(realm.getRealm()); - } + @Migration(versionFrom = "1.9.8.Final") + public void migration198Test() { + Assert.assertNames(realmResource.roles().list(), "offline_access", "uma_authorization"); + Assert.assertNames(realmResource.clients().findAll(), "admin-cli", "realm-management", "security-admin-console", "broker", "account"); //TODO } - + + /** + * Assumed that there is only one migration test for each version and *remove* + * 'Migration' realm from Keycloak after test to be able to run the rest + * of the testsuite isolated afterward. + */ + @After + public void afterMigrationTest() { + log.info("removing '" + realmRep.getRealm() + "' realm"); + removeRealm(realmRep); + } + } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/OIDCAdvancedRequestParamsTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/OIDCAdvancedRequestParamsTest.java index 191581e600..e01057d418 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/OIDCAdvancedRequestParamsTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/OIDCAdvancedRequestParamsTest.java @@ -19,30 +19,42 @@ package org.keycloak.testsuite.oidc; import java.util.List; - import org.jboss.arquillian.graphene.page.Page; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.keycloak.OAuthErrorException; +import org.keycloak.admin.client.resource.ClientResource; +import org.keycloak.authentication.authenticators.client.JWTClientAuthenticator; import org.keycloak.common.util.Time; import org.keycloak.events.Details; +import org.keycloak.jose.jws.Algorithm; import org.keycloak.models.Constants; +import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper; import org.keycloak.protocol.oidc.OIDCLoginProtocol; import org.keycloak.representations.IDToken; +import org.keycloak.representations.idm.CertificateRepresentation; +import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.EventRepresentation; import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.services.util.CertificateInfoHelper; import org.keycloak.testsuite.Assert; import org.keycloak.testsuite.AssertEvents; import org.keycloak.testsuite.TestRealmKeycloakTest; import org.keycloak.testsuite.admin.AbstractAdminTest; +import org.keycloak.testsuite.admin.ApiUtil; +import org.keycloak.testsuite.client.resources.TestApplicationResourceUrls; +import org.keycloak.testsuite.client.resources.TestOIDCEndpointsApplicationResource; import org.keycloak.testsuite.pages.AccountUpdateProfilePage; import org.keycloak.testsuite.pages.AppPage; +import org.keycloak.testsuite.pages.ErrorPage; import org.keycloak.testsuite.pages.LoginPage; import org.keycloak.testsuite.pages.OAuthGrantPage; +import org.keycloak.testsuite.rest.resource.TestingOIDCEndpointsApplicationResource; import org.keycloak.testsuite.util.ClientManager; import org.keycloak.testsuite.util.OAuthClient; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -68,6 +80,9 @@ public class OIDCAdvancedRequestParamsTest extends TestRealmKeycloakTest { @Page protected OAuthGrantPage grantPage; + @Page + protected ErrorPage errorPage; + @Override public void configureTestRealm(RealmRepresentation testRealm) { @@ -308,29 +323,98 @@ public class OIDCAdvancedRequestParamsTest extends TestRealmKeycloakTest { // REQUEST & REQUEST_URI @Test - public void requestParam() { - driver.navigate().to(oauth.getLoginFormUrl() + "&request=abc"); + public void requestParamUnsigned() throws Exception { + String validRedirectUri = oauth.getRedirectUri(); + TestOIDCEndpointsApplicationResource oidcClientEndpointsResource = testingClient.testApp().oidcClientEndpoints(); - assertFalse(loginPage.isCurrent()); + // Send request object with invalid redirect uri. + oidcClientEndpointsResource.setOIDCRequest("test", "test-app", "http://invalid", null, Algorithm.none.toString()); + String requestStr = oidcClientEndpointsResource.getOIDCRequest(); + + oauth.request(requestStr); + oauth.openLoginForm(); + Assert.assertTrue(errorPage.isCurrent()); + assertEquals("Invalid parameter: redirect_uri", errorPage.getError()); + + // Assert the value from request object has bigger priority then from the query parameter. + oauth.redirectUri("http://invalid"); + oidcClientEndpointsResource.setOIDCRequest("test", "test-app", validRedirectUri, "10", Algorithm.none.toString()); + requestStr = oidcClientEndpointsResource.getOIDCRequest(); + + oauth.request(requestStr); + OAuthClient.AuthorizationEndpointResponse response = oauth.doLogin("test-user@localhost", "password"); + Assert.assertNotNull(response.getCode()); + Assert.assertEquals("mystate", response.getState()); assertTrue(appPage.isCurrent()); - - // Assert error response was sent because not logged in - OAuthClient.AuthorizationEndpointResponse resp = new OAuthClient.AuthorizationEndpointResponse(oauth); - Assert.assertNull(resp.getCode()); - Assert.assertEquals(OAuthErrorException.REQUEST_NOT_SUPPORTED, resp.getError()); } @Test - public void requestUriParam() { - driver.navigate().to(oauth.getLoginFormUrl() + "&request_uri=https%3A%2F%2Flocalhost%3A60784%2Fexport%2FqzHTG11W48.jwt"); + public void requestUriParamUnsigned() throws Exception { + String validRedirectUri = oauth.getRedirectUri(); + TestOIDCEndpointsApplicationResource oidcClientEndpointsResource = testingClient.testApp().oidcClientEndpoints(); - assertFalse(loginPage.isCurrent()); + // Send request object with invalid redirect uri. + oidcClientEndpointsResource.setOIDCRequest("test", "test-app", "http://invalid", null, Algorithm.none.toString()); + + oauth.requestUri(TestApplicationResourceUrls.clientRequestUri()); + oauth.openLoginForm(); + Assert.assertTrue(errorPage.isCurrent()); + assertEquals("Invalid parameter: redirect_uri", errorPage.getError()); + + // Assert the value from request object has bigger priority then from the query parameter. + oauth.redirectUri("http://invalid"); + oidcClientEndpointsResource.setOIDCRequest("test", "test-app", validRedirectUri, "10", Algorithm.none.toString()); + + OAuthClient.AuthorizationEndpointResponse response = oauth.doLogin("test-user@localhost", "password"); + Assert.assertNotNull(response.getCode()); + Assert.assertEquals("mystate", response.getState()); + assertTrue(appPage.isCurrent()); + } + + @Test + public void requestUriParamSigned() throws Exception { + String validRedirectUri = oauth.getRedirectUri(); + TestOIDCEndpointsApplicationResource oidcClientEndpointsResource = testingClient.testApp().oidcClientEndpoints(); + + // Set required signature for request_uri + ClientResource clientResource = ApiUtil.findClientByClientId(adminClient.realm("test"), "test-app"); + ClientRepresentation clientRep = clientResource.toRepresentation(); + OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setRequestObjectSignatureAlg(Algorithm.RS256); + clientResource.update(clientRep); + + // Verify unsigned request_uri will fail + oidcClientEndpointsResource.setOIDCRequest("test", "test-app", validRedirectUri, "10", Algorithm.none.toString()); + oauth.requestUri(TestApplicationResourceUrls.clientRequestUri()); + oauth.openLoginForm(); + Assert.assertTrue(errorPage.isCurrent()); + assertEquals("Invalid Request", errorPage.getError()); + + // Generate keypair for client + String clientPublicKeyPem = oidcClientEndpointsResource.generateKeys().get(TestingOIDCEndpointsApplicationResource.PUBLIC_KEY); + + // Verify signed request_uri will fail due to failed signature validation + oidcClientEndpointsResource.setOIDCRequest("test", "test-app", validRedirectUri, "10", Algorithm.RS256.toString()); + oauth.openLoginForm(); + Assert.assertTrue(errorPage.isCurrent()); + assertEquals("Invalid Request", errorPage.getError()); + + + // Update clientModel with publicKey for signing + clientRep = clientResource.toRepresentation(); + CertificateRepresentation cert = new CertificateRepresentation(); + cert.setPublicKey(clientPublicKeyPem); + CertificateInfoHelper.updateClientRepresentationCertificateInfo(clientRep, cert, JWTClientAuthenticator.ATTR_PREFIX); + clientResource.update(clientRep); + + // Check signed request_uri will pass + OAuthClient.AuthorizationEndpointResponse response = oauth.doLogin("test-user@localhost", "password"); + Assert.assertNotNull(response.getCode()); + Assert.assertEquals("mystate", response.getState()); assertTrue(appPage.isCurrent()); - // Assert error response was sent because not logged in - OAuthClient.AuthorizationEndpointResponse resp = new OAuthClient.AuthorizationEndpointResponse(oauth); - Assert.assertNull(resp.getCode()); - Assert.assertEquals(OAuthErrorException.REQUEST_URI_NOT_SUPPORTED, resp.getError()); + // Revert requiring signature for client + OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setRequestObjectSignatureAlg(null); + clientResource.update(clientRep); } // LOGIN_HINT diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/OIDCWellKnownProviderTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/OIDCWellKnownProviderTest.java index 91ccc7e691..e1877b89ba 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/OIDCWellKnownProviderTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/OIDCWellKnownProviderTest.java @@ -36,6 +36,8 @@ import org.keycloak.protocol.oidc.representations.OIDCConfigurationRepresentatio import org.keycloak.protocol.oidc.utils.OIDCResponseType; import org.keycloak.representations.IDToken; import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.services.clientregistration.ClientRegistrationService; +import org.keycloak.services.clientregistration.oidc.OIDCClientRegistrationProviderFactory; import org.keycloak.services.resources.RealmsResource; import org.keycloak.testsuite.AbstractKeycloakTest; import org.keycloak.testsuite.Assert; @@ -79,14 +81,24 @@ public class OIDCWellKnownProviderTest extends AbstractKeycloakTest { Assert.assertEquals(oidcConfig.getUserinfoEndpoint(), OIDCLoginProtocolService.userInfoUrl(UriBuilder.fromUri(OAuthClient.AUTH_SERVER_ROOT)).build("test").toString()); Assert.assertEquals(oidcConfig.getJwksUri(), oauth.getCertsUrl("test")); + String registrationUri = UriBuilder + .fromUri(OAuthClient.AUTH_SERVER_ROOT) + .path(RealmsResource.class) + .path(RealmsResource.class, "getClientsService") + .path(ClientRegistrationService.class, "provider") + .build("test", OIDCClientRegistrationProviderFactory.ID) + .toString(); + Assert.assertEquals(oidcConfig.getRegistrationEndpoint(), registrationUri); + // Support standard + implicit + hybrid flow assertContains(oidcConfig.getResponseTypesSupported(), OAuth2Constants.CODE, OIDCResponseType.ID_TOKEN, "id_token token", "code id_token", "code token", "code id_token token"); assertContains(oidcConfig.getGrantTypesSupported(), OAuth2Constants.AUTHORIZATION_CODE, OAuth2Constants.IMPLICIT); assertContains(oidcConfig.getResponseModesSupported(), "query", "fragment"); - Assert.assertNames(oidcConfig.getSubjectTypesSupported(), "public"); + Assert.assertNames(oidcConfig.getSubjectTypesSupported(), "pairwise", "public"); Assert.assertNames(oidcConfig.getIdTokenSigningAlgValuesSupported(), Algorithm.RS256.toString()); Assert.assertNames(oidcConfig.getUserInfoSigningAlgValuesSupported(), Algorithm.RS256.toString()); + Assert.assertNames(oidcConfig.getRequestObjectSigningAlgValuesSupported(), Algorithm.none.toString(), Algorithm.RS256.toString()); // Client authentication Assert.assertNames(oidcConfig.getTokenEndpointAuthMethodsSupported(), "client_secret_basic", "client_secret_post", "private_key_jwt"); @@ -101,8 +113,8 @@ public class OIDCWellKnownProviderTest extends AbstractKeycloakTest { Assert.assertNames(oidcConfig.getScopesSupported(), OAuth2Constants.SCOPE_OPENID, OAuth2Constants.OFFLINE_ACCESS); // Request and Request_Uri - Assert.assertFalse(oidcConfig.getRequestParameterSupported()); - Assert.assertFalse(oidcConfig.getRequestUriParameterSupported()); + Assert.assertTrue(oidcConfig.getRequestParameterSupported()); + Assert.assertTrue(oidcConfig.getRequestUriParameterSupported()); } finally { client.close(); } diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/basic-auth/META-INF/context.xml b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/basic-auth/META-INF/context.xml new file mode 100644 index 0000000000..e626986964 --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/basic-auth/META-INF/context.xml @@ -0,0 +1,20 @@ + + + + + \ No newline at end of file diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/basic-auth/WEB-INF/jetty-web.xml b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/basic-auth/WEB-INF/jetty-web.xml new file mode 100644 index 0000000000..8c59313878 --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/basic-auth/WEB-INF/jetty-web.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/basic-auth/WEB-INF/keycloak-relative.json b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/basic-auth/WEB-INF/keycloak-relative.json new file mode 100644 index 0000000000..e00b8fcea7 --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/basic-auth/WEB-INF/keycloak-relative.json @@ -0,0 +1,11 @@ +{ + "realm" : "demo", + "resource" : "basic-auth-service", + "realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB", + "auth-server-url": "/auth", + "ssl-required" : "external", + "enable-basic-auth" : "true", + "credentials": { + "secret": "password" + } +} diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/basic-auth/WEB-INF/keycloak.json b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/basic-auth/WEB-INF/keycloak.json new file mode 100644 index 0000000000..e00b8fcea7 --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/basic-auth/WEB-INF/keycloak.json @@ -0,0 +1,11 @@ +{ + "realm" : "demo", + "resource" : "basic-auth-service", + "realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB", + "auth-server-url": "/auth", + "ssl-required" : "external", + "enable-basic-auth" : "true", + "credentials": { + "secret": "password" + } +} diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/basic-auth/WEB-INF/web.xml b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/basic-auth/WEB-INF/web.xml new file mode 100644 index 0000000000..0ea56f483f --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/basic-auth/WEB-INF/web.xml @@ -0,0 +1,46 @@ + + + + + + basic-auth + + + + /* + + + + user + + + + + KEYCLOAK + demo + + + + user + + \ No newline at end of file diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/customer-portal/WEB-INF/keycloak.json b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/customer-portal/WEB-INF/keycloak.json index b75c1d5e65..32703f9a12 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/customer-portal/WEB-INF/keycloak.json +++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/customer-portal/WEB-INF/keycloak.json @@ -1,10 +1,10 @@ { "realm": "demo", "resource": "customer-portal", - "realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB", - "auth-server-url": "http://localhost:8180/auth", + "auth-server-url": "http://localhostt:8180/auth", "ssl-required" : "external", "expose-token": true, + "min-time-between-jwks-requests": 120, "credentials": { "secret": "password" } diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/demorealm.json b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/demorealm.json index 9a4a7f6d55..027258439d 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/demorealm.json +++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/demorealm.json @@ -226,6 +226,15 @@ "/oauth-client-cdi/*" ], "secret": "password" + }, + { + "clientId": "basic-auth-service", + "standardFlowEnabled": false, + "directAccessGrantsEnabled": true, + "enabled": true, + "adminUrl": "/basic-auth", + "baseUrl": "/basic-auth", + "secret": "password" } ] } diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/input-portal/WEB-INF/keycloak.json b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/input-portal/WEB-INF/keycloak.json index a934e97ec2..eecc24bac9 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/input-portal/WEB-INF/keycloak.json +++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/input-portal/WEB-INF/keycloak.json @@ -1,9 +1,9 @@ { "realm" : "demo", "resource" : "input-portal", - "realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB", "auth-server-url" : "http://${my.host.name}:8180/auth", "ssl-required" : "external", + "min-time-between-jwks-requests": 120, "credentials" : { "secret": "password" } diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/token-min-ttl/WEB-INF/keycloak.json b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/token-min-ttl/WEB-INF/keycloak.json index 85eb9a72ab..b86e018279 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/token-min-ttl/WEB-INF/keycloak.json +++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/token-min-ttl/WEB-INF/keycloak.json @@ -1,11 +1,11 @@ { "realm": "demo", "resource": "token-min-ttl", - "realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB", "auth-server-url": "http://localhost:8180/auth", "ssl-required" : "external", "credentials": { "secret": "password" }, - "token-minimum-time-to-live": 120 + "token-minimum-time-to-live": 120, + "min-time-between-jwks-requests": 120 } \ No newline at end of file diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/token-min-ttl/WEB-INF/web.xml b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/token-min-ttl/WEB-INF/web.xml index f8110bc3cc..86d07f5ee6 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/token-min-ttl/WEB-INF/web.xml +++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/token-min-ttl/WEB-INF/web.xml @@ -68,6 +68,12 @@ /error.html + + + Unsecured + /unsecured/* + + KEYCLOAK diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/admin-test/testrealm.json b/testsuite/integration-arquillian/tests/base/src/test/resources/admin-test/testrealm.json index bc5b2a72f7..b20eb5da1f 100755 --- a/testsuite/integration-arquillian/tests/base/src/test/resources/admin-test/testrealm.json +++ b/testsuite/integration-arquillian/tests/base/src/test/resources/admin-test/testrealm.json @@ -95,5 +95,11 @@ "roles": ["customer-user"] } ] + }, + "attributes": { + "string-attr": "foo", + "int-attr": "123", + "long-attr": "1234567890123456", + "bool-attr": "true" } } diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/arquillian.xml b/testsuite/integration-arquillian/tests/base/src/test/resources/arquillian.xml index 215da8e498..8c996d603f 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/resources/arquillian.xml +++ b/testsuite/integration-arquillian/tests/base/src/test/resources/arquillian.xml @@ -150,11 +150,11 @@ - + - + - ${auth.server.jboss.kc16} + ${auth.server.jboss.migration} org.jboss.as.arquillian.container.managed.ManagedDeployableContainer ${keycloak.migration.home} @@ -164,62 +164,10 @@ -Dkeycloak.migration.strategy=OVERWRITE_EXISTING -Dkeycloak.migration.realmName=Migration -Djboss.socket.binding.port-offset=${auth.server.port.offset} - -Xms64m -Xmx512m -XX:MaxPermSize=256m + ${auth.server.memory.settings} ${auth.server.management.port} - ${auth.server.startup.timeout} - - - - - - ${auth.server.jboss.kc15} - org.jboss.as.arquillian.container.managed.ManagedDeployableContainer - ${keycloak.migration.home} - - -Dkeycloak.migration.action=import - -Dkeycloak.migration.provider=singleFile - -Dkeycloak.migration.file=${keycloak.migration.file} - -Dkeycloak.migration.strategy=OVERWRITE_EXISTING - -Dkeycloak.migration.realmName=Migration - -Djboss.socket.binding.port-offset=${auth.server.port.offset} - -Xms64m -Xmx512m -XX:MaxPermSize=256m - - ${auth.server.management.port} - ${auth.server.startup.timeout} - - - - - - ${auth.server.jboss.kc14} - org.jboss.as.arquillian.container.managed.ManagedDeployableContainer - ${keycloak.migration.home} - -Djboss.socket.binding.port-offset=${auth.server.port.offset} -Xms64m -Xmx512m -XX:MaxPermSize=256m - ${auth.server.management.port} - ${auth.server.startup.timeout} - - - - - - ${auth.server.jboss.kc13} - org.jboss.as.arquillian.container.managed.ManagedDeployableContainer - ${keycloak.migration.home} - -Djboss.socket.binding.port-offset=${auth.server.port.offset} -Xms64m -Xmx512m -XX:MaxPermSize=256m - ${auth.server.management.port} - ${auth.server.startup.timeout} - - - - - - ${auth.server.jboss.kc12} - org.jboss.as.arquillian.container.managed.ManagedDeployableContainer - ${keycloak.migration.home} - -Djboss.socket.binding.port-offset=${auth.server.port.offset} -Xms64m -Xmx512m -XX:MaxPermSize=256m - ${auth.server.management.port} - ${auth.server.startup.timeout} + ${auth.server.jboss.startup.timeout} diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/log4j.properties b/testsuite/integration-arquillian/tests/base/src/test/resources/log4j.properties index 04c79fecd3..ce33acfc86 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/resources/log4j.properties +++ b/testsuite/integration-arquillian/tests/base/src/test/resources/log4j.properties @@ -61,3 +61,5 @@ log4j.logger.org.hibernate=off log4j.logger.org.jboss.resteasy=warn log4j.logger.org.apache.directory.api=warn log4j.logger.org.apache.directory.server.core=warn + +log4j.logger.org.keycloak.authentication.authenticators.browser.IdentityProviderAuthenticator=trace \ No newline at end of file diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/migration-test/migration-realm-16.json b/testsuite/integration-arquillian/tests/base/src/test/resources/migration-test/migration-realm-1.9.8.Final.json similarity index 57% rename from testsuite/integration-arquillian/tests/base/src/test/resources/migration-test/migration-realm-16.json rename to testsuite/integration-arquillian/tests/base/src/test/resources/migration-test/migration-realm-1.9.8.Final.json index 3719d043f0..5b95d1cf26 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/resources/migration-test/migration-realm-16.json +++ b/testsuite/integration-arquillian/tests/base/src/test/resources/migration-test/migration-realm-1.9.8.Final.json @@ -4,6 +4,7 @@ "notBefore" : 0, "revokeRefreshToken" : false, "accessTokenLifespan" : 300, + "accessTokenLifespanForImplicitFlow" : 900, "ssoSessionIdleTimeout" : 1800, "ssoSessionMaxLifespan" : 36000, "offlineSessionIdleTimeout" : 2592000, @@ -25,13 +26,13 @@ "quickLoginCheckMilliSeconds" : 1000, "maxDeltaTimeSeconds" : 43200, "failureFactor" : 30, - "privateKey" : "MIIEowIBAAKCAQEAg/XlZqOYbYHyzHjWKwCD35JKloSyBBaIQgQbUjmWSBLw6xyNLSSvI45lmhoxcJTjxeZ+LKudfcoDVcLah1kYmTiS5YtNROeqmdWTkekdsAW8PYQJ0ScpatJ3jQ6xpe2E/AQWel5h6HI07O/r1mc3JDCXSe5zKdV9C0aGZpQSU2jWkVmP1cc2EZg5bVD1v057CUpKAX3qkloXpRedq6tMgwRSurhgnWDt93xWcv/+zz1Rw400batHmAm8Xa792jfZhSjvxtv9Q83Eb9jqi+c3BnxC3hucUDc6ivm8UgKYha054IOVbG8wDtmhZF8LlvLpjPk9iHuygS0zRnRuCo1+pQIDAQABAoIBACx5B8oSooFthS2CH/O4JbmIbRjTOceE7IELL0YD4HED6SvjoHSxY1EhYX6RC05871K3/pgBcn99QKh7lfh9f3vMBD3WN8FcLjPQNf67yOSU2j8FK+XQQ/YbXm0soZRhOytQGV8+RdL4AnxD04CboorQ0Xv6H9feelj9eLhDePWg5qEGHZJA6zGYiOUBALAL+SXoL59LWWLEM48TQWM0yGCA3mQM0iWCclbLNM1ls5gwMxSDdJeKC/3qlB6egGqPtXCEJdQXYqt3do8UUnxdQEkRGlJx14cSoH7fmZyZjLsEBcQT5uoAHI7/NMVN1DoFgwMTsq/MAATh3ngHqSl6J2ECgYEA3Ati3EaI0Vb8KzdjwZVgk9/KKnGLcswOl+cfU+lL9Vv5W4lVht3zvNAO8mUjtTSpjCF7LlY2lD9JEsv4cA9T3v2L7ZjkBiD6S/YYnEZYGAOjJb+LniRLqSVgN3beUgSiG/zzwuJm92J3dIcqMIPi4gdLMJ7KAv9qgbOddAy4b3kCgYEAmYXlqAu+Vgyjj3wzfgeXKzYkUfbEXUzsdugpW2gCvKi2/lJwmzORfsvSL88CxKZvzbEb1AJOrW+aPUbO0vPWo07ztphPKed+Gydp2G837f24eGZpBpxG6ATIa5MjtCHgX5guTMA3sJCFyLdMacRmlkmZEwbk7e9QxCp9HOmFoY0CgYB5u1LVya+nIBghUGM/lQP4yrVtBaO/vmPUZWhPY6FB+7/XhAJsuh09N10NfCZk+N1TSLJ8z/UhzmD+pRir5c7gbiQbLZn4SgYuP9cdnUze/CQlnfH/atTwnly8UmZruWR1V1sDVXzhHvg23w/YBx5dLOvL2gyn2+VwG43fxanDAQKBgFpFqWzOuvTOKb7NQVnyDKmUBHdNqtlRyhmBGhBtcG6OpkuVHjGkeQEdyFHkX7RSSZuhcMORN8IzxXYSlLrmNmeAnT3ZAXOac0R0QIDLpQ+ECVyCm28PpYH4jgDzXCMnaE/NpCvtOtHPqVsErSHkIo5saF4Px71A4zT15uuBRNphAoGBAMXV0eqeZOr/iHGIie3Ol0atZuB9b/BgLJBTFsCbFoVLdMAah4i2MXDm3vOUWlPf2VFL1LcKXYQ1GZ79We5LqG5w1CLA5WNt93U3yyl3/V5w02My5dVhz9BD4kWhZcvih+uVuHBxeI8Q8AvU63qqT4punQW4SSAHC+9e3U62aNc+", - "publicKey" : "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAg/XlZqOYbYHyzHjWKwCD35JKloSyBBaIQgQbUjmWSBLw6xyNLSSvI45lmhoxcJTjxeZ+LKudfcoDVcLah1kYmTiS5YtNROeqmdWTkekdsAW8PYQJ0ScpatJ3jQ6xpe2E/AQWel5h6HI07O/r1mc3JDCXSe5zKdV9C0aGZpQSU2jWkVmP1cc2EZg5bVD1v057CUpKAX3qkloXpRedq6tMgwRSurhgnWDt93xWcv/+zz1Rw400batHmAm8Xa792jfZhSjvxtv9Q83Eb9jqi+c3BnxC3hucUDc6ivm8UgKYha054IOVbG8wDtmhZF8LlvLpjPk9iHuygS0zRnRuCo1+pQIDAQAB", - "certificate" : "MIICoTCCAYkCBgFQs9TiPDANBgkqhkiG9w0BAQsFADAUMRIwEAYDVQQDDAlNaWdyYXRpb24wHhcNMTUxMDI5MTMzOTQ0WhcNMjUxMDI5MTM0MTI0WjAUMRIwEAYDVQQDDAlNaWdyYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCD9eVmo5htgfLMeNYrAIPfkkqWhLIEFohCBBtSOZZIEvDrHI0tJK8jjmWaGjFwlOPF5n4sq519ygNVwtqHWRiZOJLli01E56qZ1ZOR6R2wBbw9hAnRJylq0neNDrGl7YT8BBZ6XmHocjTs7+vWZzckMJdJ7nMp1X0LRoZmlBJTaNaRWY/VxzYRmDltUPW/TnsJSkoBfeqSWhelF52rq0yDBFK6uGCdYO33fFZy//7PPVHDjTRtq0eYCbxdrv3aN9mFKO/G2/1DzcRv2OqL5zcGfELeG5xQNzqK+bxSApiFrTngg5VsbzAO2aFkXwuW8umM+T2Ie7KBLTNGdG4KjX6lAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAIOonQw/DIwSDU2oFyoaZ7VfFcT6Uh3QXfLB4ZYDek4m6Y8onrtqzuGo2Z2tcokV9PPkBAQtefSxIqx4COC9OeDBy1DeY04AkpprywLuOzCL/OBCV9QwiMkuBYYC5JJCIyTuo3qYxkuDA/iwDdH7vNDGohE6TDYmrC3Yp3lsLaHZ1lG52Df3qL8wo3/PdwzCOmyRGfm3vscTY+PqVCYcfxg9nPkoU3FwUJDAshjMxRw7/aafG8OmQ2EeYH4HV/FsULFrwoVF0Up8TkqAhmdJJ+vaGqaHHXQyiIPTMCdrkE58jAvtpbX0XCEDiNXT9qORhmjKP8Xq13XOQTfI5DRo8Qc=", - "codeSecret" : "56227bc5-edb9-419c-a019-d61a7c6ffb74", + "privateKey" : "MIIEogIBAAKCAQEAvsWl692vnWYMFpHgMbQ6NnSMLaTMzFg5EEe3qHsm8jqh6GYIwgGvxUNYwZKKimw4D2f3+0PEHd+bDBa3Qb2WjOibUXcxgPTxXOP7awJATEeTiozxLan13Q0RSF7P7uHhyBNNGnMmQouGv3gi97SryPp0XzIY5Na3ZvHxbInyYfGE920GD+FrauV3i75588bFngCSCUcOdOuVifKkChsOhfpvxlvCzY0pP8hqMzBCqFim+KJI+6il89fBdd2uD1umwOu/vhLK28q2O+jNhKxt37jFJEhWR9D0wdilWOK7O09oCpb2g57H+4/uOd8ROOT+7cZjk+COgs2m7jMNTQniKwIDAQABAoIBAC0ymJ/sMtpAviZuw0YjZBNYw4lg3SCg2kwwIaQqIU0MdSWhXvvP+Hvs+89Iz8gB4bpMsKT4JjPJsWtIynwp1cxmo73GVpUt8y1F2mA89UzdPGh8AfwGo3iOywRxaiQ3og2L/R2CIJNtiSw941L6nCSOqhmPvfQhpuwd8Ev91s47R/FppdyUA6GG7//JepK5RfbHdSD7uRjebRE92fvAhS1jhE3AtvrmN/OZMSY7saGRMK6Ht7mFu1QKWhCpCT6EyXrJhpidIdchgf/VJOA9+wAPfQdHtAZpOstDzTo59QMYcyOTu4AaaIV03oA6Cw/1MS8kSBqFSG5w8Sgaal74RkECgYEA+o9FWybpqLnI4IbGr1yixnYlF9l5RpHbiwnNOeYPHHi4qXXm1LOzuB9nAiJuhBvHFQX4C/1a7sjQZapgHzi0WE/ZgK97PIrVAaXs+CJM1u9TnOBUwHMd+X88PNs+Ee4gC3W9mLF2rVZ8+0C7qm4kTueqxMbD7+RPPDp+Qyy/xAsCgYEAwuoMrFc4nlg8ND91Fvkk8Q7fOLEEohJSzLHbZh/ZvhXAxAjaBM985olN0tju2yq8PER4C9+Ys+QOQ9rfHZPL7Ms1NbTSkseuDIpkBbPXDaoJCmkaILgKMk2yeghuYX97AIPrK+7HwgK/wR0a1v0iTqTKoU+BHHSgSd/y8HP4DmECgYAPsq47ucJl6c3mE0ubbQhdusU0K1Hn0/eXTHPpLrq/o+id5V8i8GdQH9eKULHv4PWmIYua5LKvxzEXTYdcLWdPKSGNaHEmJ+SUh9rC6RM7XB828u8cZ5n3KbuSbIIFCZBEZ4oLaQLwdTy7WSDo2qG3t9gBIBriisFUGq4Sc1lIZQKBgBKlCtNNgJf8/r1MuKma6YK5lna40CWktRDpjoAlWdHKCOd3pUtCgcMXrT8XCzohy2HEdutD1zqV/RtWi3Mr2Rzsj0l045Ow0CBY+JpnCpI/CqBZT2uDz03iiskLl2tyI1T4SX2pWKhhVPBnFVMtYaO+NbfagBI1wcNvTbDclwThAoGAGMvfSlEsEk38t6XxzJFGiUyQvP5rVDVBOs7xQ+kRk/fH/Z6s9uWQuotMKcrbvy7gT/6BwLZgzfedB4qqgdl0TK45wi9hUUZ6IWyU1grVtkkCIxVr0XUm+u0dDUZ97gucV+qjrFwEqzY1GGQXTmX5qqPryGnp8GOqRMOyjkOjhTc=", + "publicKey" : "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvsWl692vnWYMFpHgMbQ6NnSMLaTMzFg5EEe3qHsm8jqh6GYIwgGvxUNYwZKKimw4D2f3+0PEHd+bDBa3Qb2WjOibUXcxgPTxXOP7awJATEeTiozxLan13Q0RSF7P7uHhyBNNGnMmQouGv3gi97SryPp0XzIY5Na3ZvHxbInyYfGE920GD+FrauV3i75588bFngCSCUcOdOuVifKkChsOhfpvxlvCzY0pP8hqMzBCqFim+KJI+6il89fBdd2uD1umwOu/vhLK28q2O+jNhKxt37jFJEhWR9D0wdilWOK7O09oCpb2g57H+4/uOd8ROOT+7cZjk+COgs2m7jMNTQniKwIDAQAB", + "certificate" : "MIICoTCCAYkCBgFW6dMbGDANBgkqhkiG9w0BAQsFADAUMRIwEAYDVQQDDAlNaWdyYXRpb24wHhcNMTYwOTAyMDczNDAxWhcNMjYwOTAyMDczNTQxWjAUMRIwEAYDVQQDDAlNaWdyYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC+xaXr3a+dZgwWkeAxtDo2dIwtpMzMWDkQR7eoeybyOqHoZgjCAa/FQ1jBkoqKbDgPZ/f7Q8Qd35sMFrdBvZaM6JtRdzGA9PFc4/trAkBMR5OKjPEtqfXdDRFIXs/u4eHIE00acyZCi4a/eCL3tKvI+nRfMhjk1rdm8fFsifJh8YT3bQYP4Wtq5XeLvnnzxsWeAJIJRw5065WJ8qQKGw6F+m/GW8LNjSk/yGozMEKoWKb4okj7qKXz18F13a4PW6bA67++EsrbyrY76M2ErG3fuMUkSFZH0PTB2KVY4rs7T2gKlvaDnsf7j+453xE45P7txmOT4I6CzabuMw1NCeIrAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAI+arTjuHHyLyu/D8KGa+yzPc7RlaOimuf53e3Rtih2sd9W3cid6laRIr23w39of4msYOo9UE5CJAzYhmfUvqV2jYs/veBpwYcrLk3VNxkXNvEXpgJZ/Qa8tAzfejul52+T3iced8b4iIVx4X1H7+BiBCWQzAWE+lC0F+23fPuaT7EgoR4eXSwjDTLcEihs/vfepMX+TAsEmmu8ZCsameeYrrMoT1mfPP7uyKcZDDFnujJFFekXEp/9hOkW7S1LR2/XAp5v7KwB4hdIG+ajBg4cO4ZkqpjkAwt2vgnV2QjhyE5ZoOiIr6hYvsBIBoRXkN1x/w6Rbd9X0qA2i+IUApe4=", + "codeSecret" : "fac9fa12-39d6-4dd1-bbe0-24e3d0a65af8", "roles" : { "realm" : [ { - "id" : "c4aae789-de76-4130-a06b-a28113ada698", + "id" : "d98dc6ae-7f98-4066-bce4-d8bbc38ee322", "name" : "offline_access", "description" : "${role_offline-access}", "scopeParamRequired" : true, @@ -39,114 +40,117 @@ } ], "client" : { "realm-management" : [ { - "id" : "22345bd8-afee-44c3-9958-a134e729aaa7", - "name" : "view-identity-providers", - "description" : "${role_view-identity-providers}", + "id" : "5d6ab47a-9ce2-4837-9744-3ea972d44acc", + "name" : "view-clients", + "description" : "${role_view-clients}", "scopeParamRequired" : false, "composite" : false }, { - "id" : "6c6bb910-a769-4e92-b009-db4b9ab32c67", - "name" : "manage-events", - "description" : "${role_manage-events}", - "scopeParamRequired" : false, - "composite" : false - }, { - "id" : "5327bf34-5a16-4f36-bb15-100a25aac33e", - "name" : "view-realm", - "description" : "${role_view-realm}", - "scopeParamRequired" : false, - "composite" : false - }, { - "id" : "3c52d428-e3e5-40b3-92d4-ab6195b7dce5", - "name" : "create-client", - "description" : "${role_create-client}", - "scopeParamRequired" : false, - "composite" : false - }, { - "id" : "9999e081-5321-4c19-a8ac-27cea3bbde3a", - "name" : "impersonation", - "description" : "${role_impersonation}", - "scopeParamRequired" : false, - "composite" : false - }, { - "id" : "7c857cf1-b66e-4935-8749-580062d4719a", - "name" : "manage-identity-providers", - "description" : "${role_manage-identity-providers}", - "scopeParamRequired" : false, - "composite" : false - }, { - "id" : "549d2e65-d347-4221-bde0-65fff6580fc2", - "name" : "view-events", - "description" : "${role_view-events}", - "scopeParamRequired" : false, - "composite" : false - }, { - "id" : "aa1676b8-a92a-4c99-b266-54858129942d", - "name" : "view-users", - "description" : "${role_view-users}", - "scopeParamRequired" : false, - "composite" : false - }, { - "id" : "6c9a78fa-0e37-48bf-a9b5-2062312b0f33", - "name" : "manage-clients", - "description" : "${role_manage-clients}", - "scopeParamRequired" : false, - "composite" : false - }, { - "id" : "d38072d6-66fe-4102-8d4d-b5e8e2721e43", - "name" : "manage-realm", - "description" : "${role_manage-realm}", - "scopeParamRequired" : false, - "composite" : false - }, { - "id" : "a85da016-830e-42dd-8318-3cc8c28d3382", - "name" : "manage-users", - "description" : "${role_manage-users}", - "scopeParamRequired" : false, - "composite" : false - }, { - "id" : "0ab22444-1235-4391-ac10-571b33065177", + "id" : "dc211380-6293-4d64-a638-893a26f8d97c", "name" : "realm-admin", "description" : "${role_realm-admin}", "scopeParamRequired" : false, "composite" : true, "composites" : { "client" : { - "realm-management" : [ "view-identity-providers", "manage-clients", "manage-events", "view-realm", "manage-realm", "manage-users", "create-client", "impersonation", "view-events", "manage-identity-providers", "view-clients", "view-users" ] + "realm-management" : [ "view-clients", "view-events", "view-realm", "manage-events", "manage-identity-providers", "manage-users", "manage-realm", "create-client", "manage-clients", "impersonation", "view-users", "view-identity-providers" ] } } }, { - "id" : "442fcc9e-46af-495a-9cdf-64d32dabc808", - "name" : "view-clients", - "description" : "${role_view-clients}", + "id" : "371c927a-7616-4394-a5aa-ba43d9d172dd", + "name" : "manage-clients", + "description" : "${role_manage-clients}", + "scopeParamRequired" : false, + "composite" : false + }, { + "id" : "6b6538b4-7432-49d3-ac9b-49d934552efb", + "name" : "impersonation", + "description" : "${role_impersonation}", + "scopeParamRequired" : false, + "composite" : false + }, { + "id" : "727bf71c-07c7-4601-8f88-872cc3da3ae3", + "name" : "view-events", + "description" : "${role_view-events}", + "scopeParamRequired" : false, + "composite" : false + }, { + "id" : "2d270965-1e4e-44e0-90b8-bbf0e3dfff85", + "name" : "view-realm", + "description" : "${role_view-realm}", + "scopeParamRequired" : false, + "composite" : false + }, { + "id" : "99f852cd-f534-4320-b8c3-2252667cfbff", + "name" : "manage-events", + "description" : "${role_manage-events}", + "scopeParamRequired" : false, + "composite" : false + }, { + "id" : "f2fcb9cd-66c3-4118-a72a-a8a630a8e85c", + "name" : "manage-identity-providers", + "description" : "${role_manage-identity-providers}", + "scopeParamRequired" : false, + "composite" : false + }, { + "id" : "35a32ad2-84d3-48e8-9d4d-fec93a1ea355", + "name" : "manage-users", + "description" : "${role_manage-users}", + "scopeParamRequired" : false, + "composite" : false + }, { + "id" : "84a16c8b-2225-4c2e-a36b-10b73e57fb49", + "name" : "manage-realm", + "description" : "${role_manage-realm}", + "scopeParamRequired" : false, + "composite" : false + }, { + "id" : "7e74c0ad-c222-4a98-a2fb-ce5f3cd75168", + "name" : "create-client", + "description" : "${role_create-client}", + "scopeParamRequired" : false, + "composite" : false + }, { + "id" : "6e4d912f-6abd-4fff-9f69-0e403492e61e", + "name" : "view-users", + "description" : "${role_view-users}", + "scopeParamRequired" : false, + "composite" : false + }, { + "id" : "ff1f784d-00e7-46ab-801b-d45923e52b60", + "name" : "view-identity-providers", + "description" : "${role_view-identity-providers}", "scopeParamRequired" : false, "composite" : false } ], "security-admin-console" : [ ], + "admin-cli" : [ ], "broker" : [ { - "id" : "8d46836e-eb6c-4cf5-97fe-8b1b24a69e10", + "id" : "9e61a106-0c51-4f68-9f7c-f2233a07b8b9", "name" : "read-token", "description" : "${role_read-token}", "scopeParamRequired" : false, "composite" : false } ], "account" : [ { - "id" : "40799d46-6574-4d45-a157-33cc15e3e2f1", - "name" : "manage-account", - "description" : "${role_manage-account}", + "id" : "42b51a97-a178-4232-810d-c691f0efc978", + "name" : "view-profile", + "description" : "${role_view-profile}", "scopeParamRequired" : false, "composite" : false }, { - "id" : "d6056197-e9a3-4922-8b1b-ce6e99a71a43", - "name" : "view-profile", - "description" : "${role_view-profile}", + "id" : "4f2e7b5c-8ec4-4a13-abe8-5bd25f0890ac", + "name" : "manage-account", + "description" : "${role_manage-account}", "scopeParamRequired" : false, "composite" : false } ] } }, + "groups" : [ ], "defaultRoles" : [ "offline_access" ], "requiredCredentials" : [ "password" ], + "passwordPolicy" : "hashIterations(20000)", "otpPolicyType" : "totp", "otpPolicyAlgorithm" : "HmacSHA1", "otpPolicyInitialCounter" : 0, @@ -155,46 +159,39 @@ "otpPolicyPeriod" : 30, "clientScopeMappings" : { "realm-management" : [ { + "client" : "admin-cli", + "roles" : [ "realm-admin" ] + }, { "client" : "security-admin-console", "roles" : [ "realm-admin" ] } ] }, "clients" : [ { - "id" : "cdf6e789-79b9-41ad-b4a3-f02abd2aeab6", - "clientId" : "realm-management", - "name" : "${client_realm-management}", + "id" : "7e82c993-ea0a-4ec2-813e-2744e5f9f821", + "clientId" : "account", + "name" : "${client_account}", + "baseUrl" : "/auth/realms/Migration/account", "surrogateAuthRequired" : false, "enabled" : true, "clientAuthenticatorType" : "client-secret", - "secret" : "c51e802e-e33b-431e-8e74-c2ebd4ba6abf", - "redirectUris" : [ ], + "secret" : "d42d8648-e5c0-431f-8f3e-cb0e790e6cdf", + "defaultRoles" : [ "view-profile", "manage-account" ], + "redirectUris" : [ "/auth/realms/Migration/account/*" ], "webOrigins" : [ ], "notBefore" : 0, - "bearerOnly" : true, + "bearerOnly" : false, "consentRequired" : false, + "standardFlowEnabled" : true, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : false, "serviceAccountsEnabled" : false, - "directGrantsOnly" : false, "publicClient" : false, "frontchannelLogout" : false, "attributes" : { }, "fullScopeAllowed" : false, "nodeReRegistrationTimeout" : 0, "protocolMappers" : [ { - "id" : "cfaff5c8-a0e3-42af-8dcd-f7ae6000a240", - "name" : "email", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-property-mapper", - "consentRequired" : true, - "consentText" : "${email}", - "config" : { - "user.attribute" : "email", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "email", - "jsonType.label" : "String" - } - }, { - "id" : "5a68a544-0373-4cf3-9978-aed944df478f", + "id" : "a284d825-5de2-400e-b4a7-f172b5d58eb3", "name" : "full name", "protocol" : "openid-connect", "protocolMapper" : "oidc-full-name-mapper", @@ -205,7 +202,7 @@ "access.token.claim" : "true" } }, { - "id" : "41c006db-88d6-42a6-addd-8efb535f1a7d", + "id" : "5c6e3180-55e4-4756-a480-042ca86bc2ca", "name" : "role list", "protocol" : "saml", "protocolMapper" : "saml-role-list-mapper", @@ -216,21 +213,7 @@ "attribute.name" : "Role" } }, { - "id" : "d6fd0e72-aa1e-417d-b28b-ec31946dc6fd", - "name" : "username", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-property-mapper", - "consentRequired" : true, - "consentText" : "${username}", - "config" : { - "user.attribute" : "username", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "preferred_username", - "jsonType.label" : "String" - } - }, { - "id" : "d8692a7a-366d-407d-abc1-a6f45742c47c", + "id" : "5d24d4f3-5803-4c5e-8af4-280db49021cc", "name" : "given name", "protocol" : "openid-connect", "protocolMapper" : "oidc-usermodel-property-mapper", @@ -244,7 +227,7 @@ "jsonType.label" : "String" } }, { - "id" : "54f4844b-aaa5-4260-b2aa-5dc446c8b978", + "id" : "4c6f6718-bdec-47f5-9992-f1d23462c7d5", "name" : "family name", "protocol" : "openid-connect", "protocolMapper" : "oidc-usermodel-property-mapper", @@ -257,30 +240,414 @@ "claim.name" : "family_name", "jsonType.label" : "String" } - } ] + }, { + "id" : "4e7fba71-906e-4f3c-be7f-679ef09fb968", + "name" : "username", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-property-mapper", + "consentRequired" : true, + "consentText" : "${username}", + "config" : { + "user.attribute" : "username", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "preferred_username", + "jsonType.label" : "String" + } + }, { + "id" : "80727ad9-91de-49ce-8087-448c611f8af4", + "name" : "email", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-property-mapper", + "consentRequired" : true, + "consentText" : "${email}", + "config" : { + "user.attribute" : "email", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "email", + "jsonType.label" : "String" + } + } ], + "useTemplateConfig" : false, + "useTemplateScope" : false, + "useTemplateMappers" : false }, { - "id" : "7776fa56-ab87-4638-b42b-cc9537ab2fc2", - "clientId" : "security-admin-console", - "name" : "${client_security-admin-console}", - "baseUrl" : "/auth/admin/Migration/console/index.html", + "id" : "4451c2d8-3467-46cd-8d9f-f1b48bcde4c1", + "clientId" : "admin-cli", + "name" : "${client_admin-cli}", "surrogateAuthRequired" : false, "enabled" : true, "clientAuthenticatorType" : "client-secret", - "secret" : "5e0673fa-921d-4415-9d92-3a4197d87e46", - "redirectUris" : [ "/auth/admin/Migration/console/*" ], + "secret" : "c3b62964-9f68-4c69-998b-1ee5c208515f", + "redirectUris" : [ ], "webOrigins" : [ ], "notBefore" : 0, "bearerOnly" : false, "consentRequired" : false, + "standardFlowEnabled" : false, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : true, "serviceAccountsEnabled" : false, - "directGrantsOnly" : false, "publicClient" : true, "frontchannelLogout" : false, "attributes" : { }, "fullScopeAllowed" : false, "nodeReRegistrationTimeout" : 0, "protocolMappers" : [ { - "id" : "9ed45252-c571-44fe-ac5f-b30cea378ff1", + "id" : "070e3acb-4d25-4f8f-9e37-9fc8e8696942", + "name" : "username", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-property-mapper", + "consentRequired" : true, + "consentText" : "${username}", + "config" : { + "user.attribute" : "username", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "preferred_username", + "jsonType.label" : "String" + } + }, { + "id" : "7529b783-69e0-4741-ac69-ea8f886d134b", + "name" : "role list", + "protocol" : "saml", + "protocolMapper" : "saml-role-list-mapper", + "consentRequired" : false, + "config" : { + "single" : "false", + "attribute.nameformat" : "Basic", + "attribute.name" : "Role" + } + }, { + "id" : "ee3c9db0-1dc9-49e3-baff-7aa1b0f79b22", + "name" : "email", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-property-mapper", + "consentRequired" : true, + "consentText" : "${email}", + "config" : { + "user.attribute" : "email", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "email", + "jsonType.label" : "String" + } + }, { + "id" : "b9a2ed1b-a2a1-4b86-a517-cbe23d59f599", + "name" : "family name", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-property-mapper", + "consentRequired" : true, + "consentText" : "${familyName}", + "config" : { + "user.attribute" : "lastName", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "family_name", + "jsonType.label" : "String" + } + }, { + "id" : "8548e7d4-1191-4ebe-acb4-0caba2850516", + "name" : "full name", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-full-name-mapper", + "consentRequired" : true, + "consentText" : "${fullName}", + "config" : { + "id.token.claim" : "true", + "access.token.claim" : "true" + } + }, { + "id" : "816ff5d5-d594-4daf-94c3-8e40762b4d82", + "name" : "given name", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-property-mapper", + "consentRequired" : true, + "consentText" : "${givenName}", + "config" : { + "user.attribute" : "firstName", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "given_name", + "jsonType.label" : "String" + } + } ], + "useTemplateConfig" : false, + "useTemplateScope" : false, + "useTemplateMappers" : false + }, { + "id" : "a3b3fe5c-6370-492e-8656-b42a9e2c1e56", + "clientId" : "broker", + "name" : "${client_broker}", + "surrogateAuthRequired" : false, + "enabled" : true, + "clientAuthenticatorType" : "client-secret", + "secret" : "2e474414-43ed-44ac-a339-afaa06ca3c8d", + "redirectUris" : [ ], + "webOrigins" : [ ], + "notBefore" : 0, + "bearerOnly" : false, + "consentRequired" : false, + "standardFlowEnabled" : true, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : false, + "serviceAccountsEnabled" : false, + "publicClient" : false, + "frontchannelLogout" : false, + "attributes" : { }, + "fullScopeAllowed" : false, + "nodeReRegistrationTimeout" : 0, + "protocolMappers" : [ { + "id" : "ed579581-fa9f-469c-a1f8-75a12d7b4533", + "name" : "family name", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-property-mapper", + "consentRequired" : true, + "consentText" : "${familyName}", + "config" : { + "user.attribute" : "lastName", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "family_name", + "jsonType.label" : "String" + } + }, { + "id" : "531ca32b-51fe-4fa9-ad6c-ede79b85f2a9", + "name" : "given name", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-property-mapper", + "consentRequired" : true, + "consentText" : "${givenName}", + "config" : { + "user.attribute" : "firstName", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "given_name", + "jsonType.label" : "String" + } + }, { + "id" : "821eeec5-c716-4f42-bc18-d4630d011ae1", + "name" : "email", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-property-mapper", + "consentRequired" : true, + "consentText" : "${email}", + "config" : { + "user.attribute" : "email", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "email", + "jsonType.label" : "String" + } + }, { + "id" : "c9460f3a-fb5c-43b1-a3eb-cbf881b887f9", + "name" : "username", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-property-mapper", + "consentRequired" : true, + "consentText" : "${username}", + "config" : { + "user.attribute" : "username", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "preferred_username", + "jsonType.label" : "String" + } + }, { + "id" : "fab1384f-5cfd-4543-8bab-952c44cee2f5", + "name" : "role list", + "protocol" : "saml", + "protocolMapper" : "saml-role-list-mapper", + "consentRequired" : false, + "config" : { + "single" : "false", + "attribute.nameformat" : "Basic", + "attribute.name" : "Role" + } + }, { + "id" : "e1128ee4-c9fc-4320-9a69-baa572a029ef", + "name" : "full name", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-full-name-mapper", + "consentRequired" : true, + "consentText" : "${fullName}", + "config" : { + "id.token.claim" : "true", + "access.token.claim" : "true" + } + } ], + "useTemplateConfig" : false, + "useTemplateScope" : false, + "useTemplateMappers" : false + }, { + "id" : "82814fb8-1db4-40a1-823b-f17e8dbed34e", + "clientId" : "realm-management", + "name" : "${client_realm-management}", + "surrogateAuthRequired" : false, + "enabled" : true, + "clientAuthenticatorType" : "client-secret", + "secret" : "fdac74b0-5621-4301-930a-5d4e04727987", + "redirectUris" : [ ], + "webOrigins" : [ ], + "notBefore" : 0, + "bearerOnly" : true, + "consentRequired" : false, + "standardFlowEnabled" : true, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : false, + "serviceAccountsEnabled" : false, + "publicClient" : false, + "frontchannelLogout" : false, + "attributes" : { }, + "fullScopeAllowed" : false, + "nodeReRegistrationTimeout" : 0, + "protocolMappers" : [ { + "id" : "25df2680-733a-4cea-b4ae-b0210e532a48", + "name" : "username", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-property-mapper", + "consentRequired" : true, + "consentText" : "${username}", + "config" : { + "user.attribute" : "username", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "preferred_username", + "jsonType.label" : "String" + } + }, { + "id" : "e686a4ab-733d-4f7a-8198-dbb9818a4fba", + "name" : "family name", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-property-mapper", + "consentRequired" : true, + "consentText" : "${familyName}", + "config" : { + "user.attribute" : "lastName", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "family_name", + "jsonType.label" : "String" + } + }, { + "id" : "1351a931-6fa3-43cc-8e11-f281ddd7d89f", + "name" : "full name", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-full-name-mapper", + "consentRequired" : true, + "consentText" : "${fullName}", + "config" : { + "id.token.claim" : "true", + "access.token.claim" : "true" + } + }, { + "id" : "56df66c3-d88c-47d0-b9fd-b5bfa678a3e6", + "name" : "given name", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-property-mapper", + "consentRequired" : true, + "consentText" : "${givenName}", + "config" : { + "user.attribute" : "firstName", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "given_name", + "jsonType.label" : "String" + } + }, { + "id" : "99ec226f-003e-482b-9e6b-d607fcdadd0f", + "name" : "role list", + "protocol" : "saml", + "protocolMapper" : "saml-role-list-mapper", + "consentRequired" : false, + "config" : { + "single" : "false", + "attribute.nameformat" : "Basic", + "attribute.name" : "Role" + } + }, { + "id" : "90f38a5b-eef6-4d92-8ee8-2f9430ca5657", + "name" : "email", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-property-mapper", + "consentRequired" : true, + "consentText" : "${email}", + "config" : { + "user.attribute" : "email", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "email", + "jsonType.label" : "String" + } + } ], + "useTemplateConfig" : false, + "useTemplateScope" : false, + "useTemplateMappers" : false + }, { + "id" : "e025cdec-60bc-4959-8853-25c4bab219a5", + "clientId" : "security-admin-console", + "name" : "${client_security-admin-console}", + "baseUrl" : "/auth/admin/Migration/console/index.html", + "surrogateAuthRequired" : false, + "enabled" : true, + "clientAuthenticatorType" : "client-secret", + "secret" : "a5247258-4222-4571-8d95-22cb2e85be9f", + "redirectUris" : [ "/auth/admin/Migration/console/*" ], + "webOrigins" : [ ], + "notBefore" : 0, + "bearerOnly" : false, + "consentRequired" : false, + "standardFlowEnabled" : true, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : false, + "serviceAccountsEnabled" : false, + "publicClient" : true, + "frontchannelLogout" : false, + "attributes" : { }, + "fullScopeAllowed" : false, + "nodeReRegistrationTimeout" : 0, + "protocolMappers" : [ { + "id" : "f9bd8e5a-8433-4070-b3c7-f7c35f89717e", + "name" : "given name", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-property-mapper", + "consentRequired" : true, + "consentText" : "${givenName}", + "config" : { + "user.attribute" : "firstName", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "given_name", + "jsonType.label" : "String" + } + }, { + "id" : "60569fde-f909-4ae1-9560-e4fb39332114", + "name" : "family name", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-property-mapper", + "consentRequired" : true, + "consentText" : "${familyName}", + "config" : { + "user.attribute" : "lastName", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "family_name", + "jsonType.label" : "String" + } + }, { + "id" : "5acae5e3-c798-4f4c-8e63-c69cc15f918f", + "name" : "role list", + "protocol" : "saml", + "protocolMapper" : "saml-role-list-mapper", + "consentRequired" : false, + "config" : { + "single" : "false", + "attribute.nameformat" : "Basic", + "attribute.name" : "Role" + } + }, { + "id" : "7521f6e3-9246-40bb-8879-3ecb580da104", "name" : "locale", "protocol" : "openid-connect", "protocolMapper" : "oidc-usermodel-attribute-mapper", @@ -294,32 +661,7 @@ "jsonType.label" : "String" } }, { - "id" : "d1b5694e-e9e2-4d56-9019-bc658cdcded8", - "name" : "email", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-property-mapper", - "consentRequired" : true, - "consentText" : "${email}", - "config" : { - "user.attribute" : "email", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "email", - "jsonType.label" : "String" - } - }, { - "id" : "497fee7a-23b4-4345-a872-63444a8b1a27", - "name" : "full name", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-full-name-mapper", - "consentRequired" : true, - "consentText" : "${fullName}", - "config" : { - "id.token.claim" : "true", - "access.token.claim" : "true" - } - }, { - "id" : "57881d46-deca-421e-a4c5-e023e747f68e", + "id" : "7e84d6ca-4258-494f-9e9f-5cee8b8a0b45", "name" : "username", "protocol" : "openid-connect", "protocolMapper" : "oidc-usermodel-property-mapper", @@ -333,168 +675,7 @@ "jsonType.label" : "String" } }, { - "id" : "9cfe7043-ba2b-49e2-8a1b-f1b23fcb5eb5", - "name" : "family name", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-property-mapper", - "consentRequired" : true, - "consentText" : "${familyName}", - "config" : { - "user.attribute" : "lastName", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "family_name", - "jsonType.label" : "String" - } - }, { - "id" : "c37a3c4a-8999-4111-ae2a-98954a5a8674", - "name" : "role list", - "protocol" : "saml", - "protocolMapper" : "saml-role-list-mapper", - "consentRequired" : false, - "config" : { - "single" : "false", - "attribute.nameformat" : "Basic", - "attribute.name" : "Role" - } - }, { - "id" : "a7917c74-f18a-43a0-a787-7afc7b45a247", - "name" : "given name", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-property-mapper", - "consentRequired" : true, - "consentText" : "${givenName}", - "config" : { - "user.attribute" : "firstName", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "given_name", - "jsonType.label" : "String" - } - } ] - }, { - "id" : "e7faae41-f5e8-4571-b280-5bbe0d5bcb12", - "clientId" : "broker", - "name" : "${client_broker}", - "surrogateAuthRequired" : false, - "enabled" : true, - "clientAuthenticatorType" : "client-secret", - "secret" : "b2a1f1ff-5157-4240-9354-69a6deb13ccb", - "redirectUris" : [ ], - "webOrigins" : [ ], - "notBefore" : 0, - "bearerOnly" : false, - "consentRequired" : false, - "serviceAccountsEnabled" : false, - "directGrantsOnly" : false, - "publicClient" : false, - "frontchannelLogout" : false, - "attributes" : { }, - "fullScopeAllowed" : false, - "nodeReRegistrationTimeout" : 0, - "protocolMappers" : [ { - "id" : "b843f1fd-da0a-4d49-b367-3fb39f11383b", - "name" : "family name", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-property-mapper", - "consentRequired" : true, - "consentText" : "${familyName}", - "config" : { - "user.attribute" : "lastName", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "family_name", - "jsonType.label" : "String" - } - }, { - "id" : "3b18c534-1e0a-474c-adf8-e9fbc33c05e8", - "name" : "full name", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-full-name-mapper", - "consentRequired" : true, - "consentText" : "${fullName}", - "config" : { - "id.token.claim" : "true", - "access.token.claim" : "true" - } - }, { - "id" : "b7e9db64-52f6-4aba-9437-deefab06abee", - "name" : "role list", - "protocol" : "saml", - "protocolMapper" : "saml-role-list-mapper", - "consentRequired" : false, - "config" : { - "single" : "false", - "attribute.nameformat" : "Basic", - "attribute.name" : "Role" - } - }, { - "id" : "2da52efa-e9d9-4b68-a296-0310059b7df2", - "name" : "username", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-property-mapper", - "consentRequired" : true, - "consentText" : "${username}", - "config" : { - "user.attribute" : "username", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "preferred_username", - "jsonType.label" : "String" - } - }, { - "id" : "2d2df25d-26d1-4e7c-a85a-c485ab2cc0fe", - "name" : "given name", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-property-mapper", - "consentRequired" : true, - "consentText" : "${givenName}", - "config" : { - "user.attribute" : "firstName", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "given_name", - "jsonType.label" : "String" - } - }, { - "id" : "d096910d-13ac-43a7-bad8-4d1bbfd34171", - "name" : "email", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-property-mapper", - "consentRequired" : true, - "consentText" : "${email}", - "config" : { - "user.attribute" : "email", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "email", - "jsonType.label" : "String" - } - } ] - }, { - "id" : "a2864762-7cc1-4784-a540-439e611f29ba", - "clientId" : "account", - "name" : "${client_account}", - "baseUrl" : "/auth/realms/Migration/account", - "surrogateAuthRequired" : false, - "enabled" : true, - "clientAuthenticatorType" : "client-secret", - "secret" : "68cbd7a8-3b48-4751-a396-df7ab39a2fdf", - "defaultRoles" : [ "view-profile", "manage-account" ], - "redirectUris" : [ "/auth/realms/Migration/account/*" ], - "webOrigins" : [ ], - "notBefore" : 0, - "bearerOnly" : false, - "consentRequired" : false, - "serviceAccountsEnabled" : false, - "directGrantsOnly" : false, - "publicClient" : false, - "frontchannelLogout" : false, - "attributes" : { }, - "fullScopeAllowed" : false, - "nodeReRegistrationTimeout" : 0, - "protocolMappers" : [ { - "id" : "ed2c87d0-299a-40ac-a11c-df7af41bb365", + "id" : "80d84a82-5e9e-4142-bcab-0a8b752f9f07", "name" : "email", "protocol" : "openid-connect", "protocolMapper" : "oidc-usermodel-property-mapper", @@ -508,60 +689,7 @@ "jsonType.label" : "String" } }, { - "id" : "80bc8d1f-3cb8-4362-890c-68d1a5c7263d", - "name" : "given name", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-property-mapper", - "consentRequired" : true, - "consentText" : "${givenName}", - "config" : { - "user.attribute" : "firstName", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "given_name", - "jsonType.label" : "String" - } - }, { - "id" : "af93478f-176d-4be4-be5d-78a65dd88717", - "name" : "username", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-property-mapper", - "consentRequired" : true, - "consentText" : "${username}", - "config" : { - "user.attribute" : "username", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "preferred_username", - "jsonType.label" : "String" - } - }, { - "id" : "b6c1704d-39fc-4b63-8f70-74561849654f", - "name" : "family name", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-property-mapper", - "consentRequired" : true, - "consentText" : "${familyName}", - "config" : { - "user.attribute" : "lastName", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "family_name", - "jsonType.label" : "String" - } - }, { - "id" : "928dbc26-41a1-4342-ba92-c230a85e830c", - "name" : "role list", - "protocol" : "saml", - "protocolMapper" : "saml-role-list-mapper", - "consentRequired" : false, - "config" : { - "single" : "false", - "attribute.nameformat" : "Basic", - "attribute.name" : "Role" - } - }, { - "id" : "03a967ab-ed2b-402f-ae2f-10729084376c", + "id" : "c8884632-dbfd-4b14-971c-c8fc5b08b20a", "name" : "full name", "protocol" : "openid-connect", "protocolMapper" : "oidc-full-name-mapper", @@ -571,11 +699,16 @@ "id.token.claim" : "true", "access.token.claim" : "true" } - } ] + } ], + "useTemplateConfig" : false, + "useTemplateScope" : false, + "useTemplateMappers" : false } ], + "clientTemplates" : [ ], "browserSecurityHeaders" : { - "contentSecurityPolicy" : "frame-src 'self'", - "xFrameOptions" : "SAMEORIGIN" + "xContentTypeOptions" : "nosniff", + "xFrameOptions" : "SAMEORIGIN", + "contentSecurityPolicy" : "frame-src 'self'" }, "smtpServer" : { }, "eventsEnabled" : false, @@ -583,130 +716,56 @@ "enabledEventTypes" : [ ], "adminEventsEnabled" : false, "adminEventsDetailsEnabled" : false, - "identityFederationEnabled" : false, "internationalizationEnabled" : false, "supportedLocales" : [ ], "authenticationFlows" : [ { - "alias" : "reset credentials", - "description" : "Reset credentials for a user if they forgot their password or something", + "id" : "a377c376-33e1-49d9-a395-0ec67437769c", + "alias" : "Handle Existing Account", + "description" : "Handle what to do if there is existing account with same email/username like authenticated identity provider", "providerId" : "basic-flow", - "topLevel" : true, - "builtIn" : true, - "authenticationExecutions" : [ { - "authenticator" : "reset-credentials-choose-user", - "autheticatorFlow" : false, - "requirement" : "REQUIRED", - "userSetupAllowed" : false, - "priority" : 10 - }, { - "authenticator" : "reset-credential-email", - "autheticatorFlow" : false, - "requirement" : "REQUIRED", - "userSetupAllowed" : false, - "priority" : 20 - }, { - "authenticator" : "reset-password", - "autheticatorFlow" : false, - "requirement" : "REQUIRED", - "userSetupAllowed" : false, - "priority" : 30 - }, { - "authenticator" : "reset-otp", - "autheticatorFlow" : false, - "requirement" : "OPTIONAL", - "userSetupAllowed" : false, - "priority" : 40 - } ] - }, { - "alias" : "clients", - "description" : "Base authentication for clients", - "providerId" : "client-flow", - "topLevel" : true, - "builtIn" : true, - "authenticationExecutions" : [ { - "authenticator" : "client-secret", - "autheticatorFlow" : false, - "requirement" : "ALTERNATIVE", - "userSetupAllowed" : false, - "priority" : 10 - }, { - "authenticator" : "client-jwt", - "autheticatorFlow" : false, - "requirement" : "ALTERNATIVE", - "userSetupAllowed" : false, - "priority" : 20 - } ] - }, { - "alias" : "registration form", - "description" : "registration form", - "providerId" : "form-flow", "topLevel" : false, "builtIn" : true, "authenticationExecutions" : [ { - "authenticator" : "registration-user-creation", - "autheticatorFlow" : false, + "authenticator" : "idp-confirm-link", "requirement" : "REQUIRED", + "priority" : 10, "userSetupAllowed" : false, - "priority" : 20 + "autheticatorFlow" : false }, { - "authenticator" : "registration-profile-action", - "autheticatorFlow" : false, - "requirement" : "REQUIRED", + "authenticator" : "idp-email-verification", + "requirement" : "ALTERNATIVE", + "priority" : 20, "userSetupAllowed" : false, - "priority" : 40 + "autheticatorFlow" : false }, { - "authenticator" : "registration-password-action", - "autheticatorFlow" : false, - "requirement" : "REQUIRED", + "requirement" : "ALTERNATIVE", + "priority" : 30, + "flowAlias" : "Verify Existing Account by Re-authentication", "userSetupAllowed" : false, - "priority" : 50 - }, { - "authenticator" : "registration-recaptcha-action", - "autheticatorFlow" : false, - "requirement" : "DISABLED", - "userSetupAllowed" : false, - "priority" : 60 + "autheticatorFlow" : true } ] }, { - "alias" : "direct grant", - "description" : "OpenID Connect Resource Owner Grant", + "id" : "20facc1e-c957-4835-ad3a-3a4fe887293c", + "alias" : "Verify Existing Account by Re-authentication", + "description" : "Reauthentication of existing account", "providerId" : "basic-flow", - "topLevel" : true, + "topLevel" : false, "builtIn" : true, "authenticationExecutions" : [ { - "authenticator" : "direct-grant-validate-username", - "autheticatorFlow" : false, + "authenticator" : "idp-username-password-form", "requirement" : "REQUIRED", + "priority" : 10, "userSetupAllowed" : false, - "priority" : 10 + "autheticatorFlow" : false }, { - "authenticator" : "direct-grant-validate-password", - "autheticatorFlow" : false, - "requirement" : "REQUIRED", - "userSetupAllowed" : false, - "priority" : 20 - }, { - "authenticator" : "direct-grant-validate-otp", - "autheticatorFlow" : false, + "authenticator" : "auth-otp-form", "requirement" : "OPTIONAL", + "priority" : 20, "userSetupAllowed" : false, - "priority" : 30 - } ] - }, { - "alias" : "registration", - "description" : "registration flow", - "providerId" : "basic-flow", - "topLevel" : true, - "builtIn" : true, - "authenticationExecutions" : [ { - "authenticator" : "registration-page-form", - "flowAlias" : "registration form", - "autheticatorFlow" : true, - "requirement" : "REQUIRED", - "userSetupAllowed" : false, - "priority" : 10 + "autheticatorFlow" : false } ] }, { + "id" : "eab28028-9b70-4226-bfe5-d107e3e2f8d8", "alias" : "browser", "description" : "browser based authentication", "providerId" : "basic-flow", @@ -714,24 +773,99 @@ "builtIn" : true, "authenticationExecutions" : [ { "authenticator" : "auth-cookie", - "autheticatorFlow" : false, "requirement" : "ALTERNATIVE", + "priority" : 10, "userSetupAllowed" : false, - "priority" : 10 + "autheticatorFlow" : false }, { "authenticator" : "auth-spnego", - "autheticatorFlow" : false, "requirement" : "DISABLED", + "priority" : 20, "userSetupAllowed" : false, - "priority" : 20 + "autheticatorFlow" : false }, { - "flowAlias" : "forms", - "autheticatorFlow" : true, "requirement" : "ALTERNATIVE", + "priority" : 30, + "flowAlias" : "forms", "userSetupAllowed" : false, - "priority" : 30 + "autheticatorFlow" : true } ] }, { + "id" : "c0f484bf-1367-4319-b0a1-f8ff74822eb7", + "alias" : "clients", + "description" : "Base authentication for clients", + "providerId" : "client-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "client-secret", + "requirement" : "ALTERNATIVE", + "priority" : 10, + "userSetupAllowed" : false, + "autheticatorFlow" : false + }, { + "authenticator" : "client-jwt", + "requirement" : "ALTERNATIVE", + "priority" : 20, + "userSetupAllowed" : false, + "autheticatorFlow" : false + } ] + }, { + "id" : "eed6b9c1-5a3a-4ec1-acec-549da4ba1be4", + "alias" : "direct grant", + "description" : "OpenID Connect Resource Owner Grant", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "direct-grant-validate-username", + "requirement" : "REQUIRED", + "priority" : 10, + "userSetupAllowed" : false, + "autheticatorFlow" : false + }, { + "authenticator" : "direct-grant-validate-password", + "requirement" : "REQUIRED", + "priority" : 20, + "userSetupAllowed" : false, + "autheticatorFlow" : false + }, { + "authenticator" : "direct-grant-validate-otp", + "requirement" : "OPTIONAL", + "priority" : 30, + "userSetupAllowed" : false, + "autheticatorFlow" : false + } ] + }, { + "id" : "51b007c2-f394-44ae-af20-b9875d40fc15", + "alias" : "first broker login", + "description" : "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticatorConfig" : "review profile config", + "authenticator" : "idp-review-profile", + "requirement" : "REQUIRED", + "priority" : 10, + "userSetupAllowed" : false, + "autheticatorFlow" : false + }, { + "authenticatorConfig" : "create unique user config", + "authenticator" : "idp-create-user-if-unique", + "requirement" : "ALTERNATIVE", + "priority" : 20, + "userSetupAllowed" : false, + "autheticatorFlow" : false + }, { + "requirement" : "ALTERNATIVE", + "priority" : 30, + "flowAlias" : "Handle Existing Account", + "userSetupAllowed" : false, + "autheticatorFlow" : true + } ] + }, { + "id" : "da947d83-d94f-47bc-a849-387d51eda02e", "alias" : "forms", "description" : "Username, password, otp and other auth forms.", "providerId" : "basic-flow", @@ -739,27 +873,125 @@ "builtIn" : true, "authenticationExecutions" : [ { "authenticator" : "auth-username-password-form", - "autheticatorFlow" : false, "requirement" : "REQUIRED", + "priority" : 10, "userSetupAllowed" : false, - "priority" : 10 + "autheticatorFlow" : false }, { "authenticator" : "auth-otp-form", - "autheticatorFlow" : false, "requirement" : "OPTIONAL", + "priority" : 20, "userSetupAllowed" : false, - "priority" : 20 + "autheticatorFlow" : false + } ] + }, { + "id" : "1f5adf60-276b-4ae1-bb87-9ffdb419f44a", + "alias" : "registration", + "description" : "registration flow", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "registration-page-form", + "requirement" : "REQUIRED", + "priority" : 10, + "flowAlias" : "registration form", + "userSetupAllowed" : false, + "autheticatorFlow" : true + } ] + }, { + "id" : "84ae147c-bb2d-48b3-b73e-b606e9c24795", + "alias" : "registration form", + "description" : "registration form", + "providerId" : "form-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "registration-user-creation", + "requirement" : "REQUIRED", + "priority" : 20, + "userSetupAllowed" : false, + "autheticatorFlow" : false + }, { + "authenticator" : "registration-profile-action", + "requirement" : "REQUIRED", + "priority" : 40, + "userSetupAllowed" : false, + "autheticatorFlow" : false + }, { + "authenticator" : "registration-password-action", + "requirement" : "REQUIRED", + "priority" : 50, + "userSetupAllowed" : false, + "autheticatorFlow" : false + }, { + "authenticator" : "registration-recaptcha-action", + "requirement" : "DISABLED", + "priority" : 60, + "userSetupAllowed" : false, + "autheticatorFlow" : false + } ] + }, { + "id" : "d2851906-562c-4b4a-8c93-a5164f3800db", + "alias" : "reset credentials", + "description" : "Reset credentials for a user if they forgot their password or something", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "reset-credentials-choose-user", + "requirement" : "REQUIRED", + "priority" : 10, + "userSetupAllowed" : false, + "autheticatorFlow" : false + }, { + "authenticator" : "reset-credential-email", + "requirement" : "REQUIRED", + "priority" : 20, + "userSetupAllowed" : false, + "autheticatorFlow" : false + }, { + "authenticator" : "reset-password", + "requirement" : "REQUIRED", + "priority" : 30, + "userSetupAllowed" : false, + "autheticatorFlow" : false + }, { + "authenticator" : "reset-otp", + "requirement" : "OPTIONAL", + "priority" : 40, + "userSetupAllowed" : false, + "autheticatorFlow" : false + } ] + }, { + "id" : "1620a020-4499-437b-a6ee-9189d8aced3e", + "alias" : "saml ecp", + "description" : "SAML ECP Profile Authentication Flow", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "http-basic-authenticator", + "requirement" : "REQUIRED", + "priority" : 10, + "userSetupAllowed" : false, + "autheticatorFlow" : false } ] } ], - "authenticatorConfig" : [ ], - "requiredActions" : [ { - "alias" : "terms_and_conditions", - "name" : "Terms and Conditions", - "providerId" : "terms_and_conditions", - "enabled" : false, - "defaultAction" : false, - "config" : { } + "authenticatorConfig" : [ { + "id" : "a4d4a0ef-28f1-45ba-af2b-ecdcec16f0db", + "alias" : "create unique user config", + "config" : { + "require.password.update.after.registration" : "false" + } }, { + "id" : "904be702-e5e3-40f3-854d-6f598fea530b", + "alias" : "review profile config", + "config" : { + "update.profile.on.first.login" : "missing" + } + } ], + "requiredActions" : [ { "alias" : "CONFIGURE_TOTP", "name" : "Configure Totp", "providerId" : "CONFIGURE_TOTP", @@ -787,10 +1019,18 @@ "enabled" : true, "defaultAction" : false, "config" : { } + }, { + "alias" : "terms_and_conditions", + "name" : "Terms and Conditions", + "providerId" : "terms_and_conditions", + "enabled" : false, + "defaultAction" : false, + "config" : { } } ], "browserFlow" : "browser", "registrationFlow" : "registration", "directGrantFlow" : "direct grant", "resetCredentialsFlow" : "reset credentials", - "clientAuthenticationFlow" : "clients" + "clientAuthenticationFlow" : "clients", + "keycloakVersion" : "7.0.0.GA" } \ No newline at end of file diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/migration-test/migration-realm-15.json b/testsuite/integration-arquillian/tests/base/src/test/resources/migration-test/migration-realm-15.json deleted file mode 100644 index 86e4606f94..0000000000 --- a/testsuite/integration-arquillian/tests/base/src/test/resources/migration-test/migration-realm-15.json +++ /dev/null @@ -1,751 +0,0 @@ -{ - "id" : "9c3a9824-cc8b-46f6-8922-cd576a92850f", - "realm" : "Migration", - "notBefore" : 0, - "accessTokenLifespan" : 300, - "ssoSessionIdleTimeout" : 1800, - "ssoSessionMaxLifespan" : 36000, - "accessCodeLifespan" : 60, - "accessCodeLifespanUserAction" : 300, - "accessCodeLifespanLogin" : 1800, - "enabled" : true, - "sslRequired" : "external", - "registrationAllowed" : false, - "registrationEmailAsUsername" : false, - "rememberMe" : false, - "verifyEmail" : false, - "resetPasswordAllowed" : false, - "editUsernameAllowed" : false, - "bruteForceProtected" : false, - "maxFailureWaitSeconds" : 900, - "minimumQuickLoginWaitSeconds" : 60, - "waitIncrementSeconds" : 60, - "quickLoginCheckMilliSeconds" : 1000, - "maxDeltaTimeSeconds" : 43200, - "failureFactor" : 30, - "privateKey" : "MIIEpAIBAAKCAQEA29+/bYOEg+RFlDgKjX0nv+UMkV8X06E1XvRobuQjXKOV613VJIa1F/nGabXthkM3tC7DadJ5y1tBwhF+bJzMA4w38zNfJdjEp3DRND6ypUn0SJZrSw6l3u3w+s5uemgTWUZk463Xr3HbDxtnG+4t5GuHA2Oq6O2OLniVZKbDTpgF1HxzCBQiAxi2jNJm3tMlTdN6D/nV3Rwp2T1250T3ldkM3TDK/Nlup3oOejy+qRGEmh+omuABOOJ8icCULZ5S2AbiqfojP5ZN3WEpyCqcQvsdop4IawUbTDyy9BCE2K5CCZ6ZgQaSnpJZGUy91crPJXnI4tlg5Mh88l8aSrBLsQIDAQABAoIBAA5J7SPNzzfPBuKJ/c2SG5ox5W4xEthS+qfwFDVYqB+mFeEU2PwlsPEc71MBWq1GAwG3pEVlQzr+9DgLcP7X9b4pR52LchyAiM8k2sOda3ioZLKu68wV6JujNOznq3BTASblFztgmcqyCH1j14COKvdUMZL70CiQ/5NvjK3c1IZv5d/S9B7Qhd2o/6cO51xIodE87Lc4Pghq8cQ/AJJUJokyFtjkCpTNAYxcZgyiEMNbyjrbNMMEpiuspZ50eRbi7SOKOg6mSjwuTeK0cQ57JDuMhE/iyaMwh98uqSTccqeKS672z+7QCu89ce1YZMnWtjfwKEiIcTWB71pvy2gGwgECgYEA9Afv+5Zop4j1kmZvQcdr+UpW3Ia91nNelvlkMYPMrsC24xwrGhO9Hx76VxdBFCzDuYBIyOzbPLV7kFojSKmcWB6hb/S/j6eMd46ZetycrfH5sRpJHmqJpGZiARrWTLsFRNDwi2jwEl2qt3wkq/IBvuzNt9bwbnsajgRVSVWUBtECgYEA5qh+hl1f8R6KcrUro9kSSxjmlqzSIeAYNJ0VJkr2ZjRd984xTRSnUcuVJnbfNgfmywCB9s7QGgcMrs9BejKuP1bq2hnjjA1WOvz0Dq1FRw3wqYSZWHtVO2h/QDaKIcGjQ/PyWAyrOTBaL+bzKrNO66L7CQK63A4/Gj7QivFA7uECgYEAmYW81pyDbpLdW6MR72IUbZr1Fnu2RooCQhzXiccPKAmZhTudaiRs4H1OpSe+C4E2CSfJoo5QRtstx1zNwdLixxVOHu7s7OVNm5GcwQy1jUEkAuU0huwjd8fpdCR8GX23DNod2rbEAennktOJBpuTuZekvDl+vSK5TAsx1JcAL2ECgYB7rPTKjt6Wps2NW98eZ5ILejqJp/iz+TiBXYitk5wyiPmpmYGN1vkwPnymty5QBkSVrJwC/jlO+2CtiquNHgeYJr6eWytLOQt3bZJfHED9LFhSTKr8aoT06b7xa0z9dJpaIT9cPs7AR1DURn0z9Bjo9+aqmjAfNfRX2j5vgZRTgQKBgQC7+9bt4yZ0MAxJYTMVqU/LnyjPuDrgXZJYw5ZYO6r5xF0mdovE9+lY6I8OeAUg428Zk8mxMYeqOFUHF8nVBxofHrZbXR2eJxJLRO8f2GPRFYanA9MNe1Jc0WV5bi1gF+ifC0j//W1kGxCHJX1OeMSV/h8r3OaIHEwuu30ZLHFxRg==", - "publicKey" : "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA29+/bYOEg+RFlDgKjX0nv+UMkV8X06E1XvRobuQjXKOV613VJIa1F/nGabXthkM3tC7DadJ5y1tBwhF+bJzMA4w38zNfJdjEp3DRND6ypUn0SJZrSw6l3u3w+s5uemgTWUZk463Xr3HbDxtnG+4t5GuHA2Oq6O2OLniVZKbDTpgF1HxzCBQiAxi2jNJm3tMlTdN6D/nV3Rwp2T1250T3ldkM3TDK/Nlup3oOejy+qRGEmh+omuABOOJ8icCULZ5S2AbiqfojP5ZN3WEpyCqcQvsdop4IawUbTDyy9BCE2K5CCZ6ZgQaSnpJZGUy91crPJXnI4tlg5Mh88l8aSrBLsQIDAQAB", - "certificate" : "MIICoTCCAYkCBgFQs81zNDANBgkqhkiG9w0BAQsFADAUMRIwEAYDVQQDDAlNaWdyYXRpb24wHhcNMTUxMDI5MTMzMTM3WhcNMjUxMDI5MTMzMzE3WjAUMRIwEAYDVQQDDAlNaWdyYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDb379tg4SD5EWUOAqNfSe/5QyRXxfToTVe9Ghu5CNco5XrXdUkhrUX+cZpte2GQze0LsNp0nnLW0HCEX5snMwDjDfzM18l2MSncNE0PrKlSfRIlmtLDqXe7fD6zm56aBNZRmTjrdevcdsPG2cb7i3ka4cDY6ro7Y4ueJVkpsNOmAXUfHMIFCIDGLaM0mbe0yVN03oP+dXdHCnZPXbnRPeV2QzdMMr82W6neg56PL6pEYSaH6ia4AE44nyJwJQtnlLYBuKp+iM/lk3dYSnIKpxC+x2inghrBRtMPLL0EITYrkIJnpmBBpKeklkZTL3Vys8lecji2WDkyHzyXxpKsEuxAgMBAAEwDQYJKoZIhvcNAQELBQADggEBALaDK+wutEjdgY3Ux06Amp0k5qK16dz4jn+QKjdKPB1yThfzY1pisuyCUXPBlkn1OjB5ZvYl6ouwdNXgB8aeblbHZoyXh9ODeywi1xZd7pGxNXSfx0UzRk/YEEy0DAi9pxTyRYxiZ6/XJalS9PembTQvj+mVKqg1SDv7dyv4byvndEYSaUISrtGGrM3bb68PW4zInD793PJYWDSVxmEPOYtdgBJv4HAhPIJhjw15EOGlPv5QxW9P76OgISCutHaEe3UDP+TzIBBxYQFb1ZXA6ob3TFga78mFAkY4g98gEC11QSvZqhaRtLAz6PEisHRV+xDJVROgQ4Qew4qKgwE0gGE=", - "codeSecret" : "32f8634c-2be2-4d4d-8118-f4f7fee80b9f", - "roles" : { - "client" : { - "realm-management" : [ { - "id" : "dad7b3a4-b533-47c8-aba5-32e6429865a2", - "name" : "manage-identity-providers", - "description" : "${role_manage-identity-providers}", - "composite" : false - }, { - "id" : "a1dd3971-3906-4f4a-b4cd-3a198d2d7150", - "name" : "view-users", - "description" : "${role_view-users}", - "composite" : false - }, { - "id" : "6c2d766f-cfa5-4cae-b1ca-81f1f9f242c8", - "name" : "view-clients", - "description" : "${role_view-clients}", - "composite" : false - }, { - "id" : "60bb5b3e-8067-43fe-803e-a7e367967c7c", - "name" : "manage-realm", - "description" : "${role_manage-realm}", - "composite" : false - }, { - "id" : "c55cb35a-2602-47a6-a628-fc5a55341426", - "name" : "manage-users", - "description" : "${role_manage-users}", - "composite" : false - }, { - "id" : "4dc834d0-766b-45aa-ab3b-b7b976baa65d", - "name" : "realm-admin", - "description" : "${role_realm-admin}", - "composite" : true, - "composites" : { - "client" : { - "realm-management" : [ "view-users", "manage-identity-providers", "view-clients", "manage-realm", "manage-users", "impersonation", "view-realm", "view-events", "manage-clients", "manage-events", "view-identity-providers" ] - } - } - }, { - "id" : "d444a98f-ab5e-4857-9300-496e04e498f5", - "name" : "impersonation", - "description" : "${role_impersonation}", - "composite" : false - }, { - "id" : "2f6f1407-f334-434f-becf-771e3ebb5625", - "name" : "view-realm", - "description" : "${role_view-realm}", - "composite" : false - }, { - "id" : "a40d3211-5244-4d92-80c0-0d3215580250", - "name" : "manage-events", - "description" : "${role_manage-events}", - "composite" : false - }, { - "id" : "d11c407e-504f-4923-b243-e794afa0247e", - "name" : "view-events", - "description" : "${role_view-events}", - "composite" : false - }, { - "id" : "3ef6ace4-4e87-4c30-a8b3-1f0df25868c6", - "name" : "manage-clients", - "description" : "${role_manage-clients}", - "composite" : false - }, { - "id" : "fa2a4972-b8d0-452e-8e13-d2cf7eaac7aa", - "name" : "view-identity-providers", - "description" : "${role_view-identity-providers}", - "composite" : false - } ], - "security-admin-console" : [ ], - "broker" : [ { - "id" : "1bc5aeb4-1df1-4402-8195-e2a72f6dca30", - "name" : "read-token", - "description" : "${role_read-token}", - "composite" : false - } ], - "account" : [ { - "id" : "71b5b5ff-b372-41a1-a427-7883fa64a8c7", - "name" : "manage-account", - "description" : "${role_manage-account}", - "composite" : false - }, { - "id" : "04daa556-8aeb-43ba-99c6-b393ec2a32d4", - "name" : "view-profile", - "description" : "${role_view-profile}", - "composite" : false - } ] - } - }, - "requiredCredentials" : [ "password" ], - "otpPolicyType" : "totp", - "otpPolicyAlgorithm" : "HmacSHA1", - "otpPolicyInitialCounter" : 0, - "otpPolicyDigits" : 6, - "otpPolicyLookAheadWindow" : 1, - "otpPolicyPeriod" : 30, - "clientScopeMappings" : { - "realm-management" : [ { - "client" : "security-admin-console", - "roles" : [ "realm-admin" ] - } ] - }, - "clients" : [ { - "id" : "ba27336f-3f89-471d-98d2-b8856bd6dbf1", - "clientId" : "realm-management", - "name" : "${client_realm-management}", - "surrogateAuthRequired" : false, - "enabled" : true, - "clientAuthenticatorType" : "client-secret", - "secret" : "26aee4e9-8eec-421b-90a9-238538f5897a", - "redirectUris" : [ ], - "webOrigins" : [ ], - "notBefore" : 0, - "bearerOnly" : true, - "consentRequired" : false, - "serviceAccountsEnabled" : false, - "directGrantsOnly" : false, - "publicClient" : false, - "frontchannelLogout" : false, - "attributes" : { }, - "fullScopeAllowed" : false, - "nodeReRegistrationTimeout" : 0, - "protocolMappers" : [ { - "id" : "5d56eba1-724e-4904-a8f8-86ca264a82cf", - "name" : "family name", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-property-mapper", - "consentRequired" : true, - "consentText" : "${familyName}", - "config" : { - "user.attribute" : "lastName", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "family_name", - "Claim JSON Type" : "String" - } - }, { - "id" : "18a30786-89f9-4744-8f36-4de811a591ae", - "name" : "email", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-property-mapper", - "consentRequired" : true, - "consentText" : "${email}", - "config" : { - "user.attribute" : "email", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "email", - "Claim JSON Type" : "String" - } - }, { - "id" : "4d1c4456-0c0d-49b9-bfba-c2c83645aeb2", - "name" : "username", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-property-mapper", - "consentRequired" : true, - "consentText" : "${username}", - "config" : { - "user.attribute" : "username", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "preferred_username", - "Claim JSON Type" : "String" - } - }, { - "id" : "1a19db43-2346-4a24-b6f0-1b8d7fc1353e", - "name" : "role list", - "protocol" : "saml", - "protocolMapper" : "saml-role-list-mapper", - "consentRequired" : false, - "config" : { - "single" : "false", - "attribute.nameformat" : "Basic", - "attribute.name" : "Role" - } - }, { - "id" : "a18612f0-9eb3-4d81-af0c-b0749b83fbd3", - "name" : "given name", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-property-mapper", - "consentRequired" : true, - "consentText" : "${givenName}", - "config" : { - "user.attribute" : "firstName", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "given_name", - "Claim JSON Type" : "String" - } - }, { - "id" : "70c26044-c7fc-4090-98e1-670fef006e25", - "name" : "full name", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-full-name-mapper", - "consentRequired" : true, - "consentText" : "${fullName}", - "config" : { - "id.token.claim" : "true", - "access.token.claim" : "true" - } - } ] - }, { - "id" : "3fdddd5e-0022-4f6d-8fdf-212266db7fd4", - "clientId" : "security-admin-console", - "name" : "${client_security-admin-console}", - "baseUrl" : "/auth/admin/Migration/console/index.html", - "surrogateAuthRequired" : false, - "enabled" : true, - "clientAuthenticatorType" : "client-secret", - "secret" : "38ce8135-738d-4103-85ac-c3470ac8824d", - "redirectUris" : [ "/auth/admin/Migration/console/*" ], - "webOrigins" : [ ], - "notBefore" : 0, - "bearerOnly" : false, - "consentRequired" : false, - "serviceAccountsEnabled" : false, - "directGrantsOnly" : false, - "publicClient" : true, - "frontchannelLogout" : false, - "attributes" : { }, - "fullScopeAllowed" : false, - "nodeReRegistrationTimeout" : 0, - "protocolMappers" : [ { - "id" : "be58fe30-b767-4566-9192-a4fa81fafa2c", - "name" : "role list", - "protocol" : "saml", - "protocolMapper" : "saml-role-list-mapper", - "consentRequired" : false, - "config" : { - "single" : "false", - "attribute.nameformat" : "Basic", - "attribute.name" : "Role" - } - }, { - "id" : "c97523fd-fd4f-48d7-8937-bd434fa374fd", - "name" : "given name", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-property-mapper", - "consentRequired" : true, - "consentText" : "${givenName}", - "config" : { - "user.attribute" : "firstName", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "given_name", - "Claim JSON Type" : "String" - } - }, { - "id" : "2323a85d-2686-46d4-bea8-e36524920f2e", - "name" : "email", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-property-mapper", - "consentRequired" : true, - "consentText" : "${email}", - "config" : { - "user.attribute" : "email", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "email", - "Claim JSON Type" : "String" - } - }, { - "id" : "56f8a80e-9e99-4add-b918-b864ca3f6f5c", - "name" : "username", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-property-mapper", - "consentRequired" : true, - "consentText" : "${username}", - "config" : { - "user.attribute" : "username", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "preferred_username", - "Claim JSON Type" : "String" - } - }, { - "id" : "f9f51e8d-d5af-456c-be5a-3019fb8c0910", - "name" : "family name", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-property-mapper", - "consentRequired" : true, - "consentText" : "${familyName}", - "config" : { - "user.attribute" : "lastName", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "family_name", - "Claim JSON Type" : "String" - } - }, { - "id" : "3d8fbb0c-9058-4dde-b675-ca77a153ceb8", - "name" : "full name", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-full-name-mapper", - "consentRequired" : true, - "consentText" : "${fullName}", - "config" : { - "id.token.claim" : "true", - "access.token.claim" : "true" - } - } ] - }, { - "id" : "068fcf1a-7048-43df-b3dd-e6c484e8b051", - "clientId" : "broker", - "name" : "${client_broker}", - "surrogateAuthRequired" : false, - "enabled" : true, - "clientAuthenticatorType" : "client-secret", - "secret" : "041b50a6-54b5-4cff-84ef-1b7c388d3395", - "redirectUris" : [ ], - "webOrigins" : [ ], - "notBefore" : 0, - "bearerOnly" : false, - "consentRequired" : false, - "serviceAccountsEnabled" : false, - "directGrantsOnly" : false, - "publicClient" : false, - "frontchannelLogout" : false, - "attributes" : { }, - "fullScopeAllowed" : false, - "nodeReRegistrationTimeout" : 0, - "protocolMappers" : [ { - "id" : "57c6f779-c96b-4f03-b268-354af2a8731e", - "name" : "email", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-property-mapper", - "consentRequired" : true, - "consentText" : "${email}", - "config" : { - "user.attribute" : "email", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "email", - "Claim JSON Type" : "String" - } - }, { - "id" : "f17a7b9f-9363-44bd-8320-df36f22ca712", - "name" : "given name", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-property-mapper", - "consentRequired" : true, - "consentText" : "${givenName}", - "config" : { - "user.attribute" : "firstName", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "given_name", - "Claim JSON Type" : "String" - } - }, { - "id" : "ec1bf022-9e98-4f29-9bf0-f0a49bd844ad", - "name" : "role list", - "protocol" : "saml", - "protocolMapper" : "saml-role-list-mapper", - "consentRequired" : false, - "config" : { - "single" : "false", - "attribute.nameformat" : "Basic", - "attribute.name" : "Role" - } - }, { - "id" : "0d54c616-326b-4fe7-bbfa-af9a28304dc5", - "name" : "username", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-property-mapper", - "consentRequired" : true, - "consentText" : "${username}", - "config" : { - "user.attribute" : "username", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "preferred_username", - "Claim JSON Type" : "String" - } - }, { - "id" : "89ce95b9-a268-4306-a1ad-86066d0cdd03", - "name" : "family name", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-property-mapper", - "consentRequired" : true, - "consentText" : "${familyName}", - "config" : { - "user.attribute" : "lastName", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "family_name", - "Claim JSON Type" : "String" - } - }, { - "id" : "8236274d-af69-4fc4-8804-a02d4af66157", - "name" : "full name", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-full-name-mapper", - "consentRequired" : true, - "consentText" : "${fullName}", - "config" : { - "id.token.claim" : "true", - "access.token.claim" : "true" - } - } ] - }, { - "id" : "717b9e58-87ed-402d-a8f8-a37fd5e7c951", - "clientId" : "account", - "name" : "${client_account}", - "baseUrl" : "/auth/realms/Migration/account", - "surrogateAuthRequired" : false, - "enabled" : true, - "clientAuthenticatorType" : "client-secret", - "secret" : "9ea62eb5-5478-454a-a479-4012f8967f9c", - "defaultRoles" : [ "view-profile", "manage-account" ], - "redirectUris" : [ "/auth/realms/Migration/account/*" ], - "webOrigins" : [ ], - "notBefore" : 0, - "bearerOnly" : false, - "consentRequired" : false, - "serviceAccountsEnabled" : false, - "directGrantsOnly" : false, - "publicClient" : false, - "frontchannelLogout" : false, - "attributes" : { }, - "fullScopeAllowed" : false, - "nodeReRegistrationTimeout" : 0, - "protocolMappers" : [ { - "id" : "bfb4a165-2a55-4e4b-9b13-05e68822f5d6", - "name" : "family name", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-property-mapper", - "consentRequired" : true, - "consentText" : "${familyName}", - "config" : { - "user.attribute" : "lastName", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "family_name", - "Claim JSON Type" : "String" - } - }, { - "id" : "c505e313-d478-4b1d-94df-c2c9b6036a95", - "name" : "role list", - "protocol" : "saml", - "protocolMapper" : "saml-role-list-mapper", - "consentRequired" : false, - "config" : { - "single" : "false", - "attribute.nameformat" : "Basic", - "attribute.name" : "Role" - } - }, { - "id" : "12fc43f0-19b7-4b4a-b50a-40b6fc344ede", - "name" : "email", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-property-mapper", - "consentRequired" : true, - "consentText" : "${email}", - "config" : { - "user.attribute" : "email", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "email", - "Claim JSON Type" : "String" - } - }, { - "id" : "250f3bf3-2655-4482-a814-3adcc7cef5a4", - "name" : "full name", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-full-name-mapper", - "consentRequired" : true, - "consentText" : "${fullName}", - "config" : { - "id.token.claim" : "true", - "access.token.claim" : "true" - } - }, { - "id" : "6cf70d19-6a9e-4abf-8917-38b87bac15d6", - "name" : "username", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-property-mapper", - "consentRequired" : true, - "consentText" : "${username}", - "config" : { - "user.attribute" : "username", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "preferred_username", - "Claim JSON Type" : "String" - } - }, { - "id" : "6cdeae21-ca97-4723-b880-d5aa35fa77b0", - "name" : "given name", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-property-mapper", - "consentRequired" : true, - "consentText" : "${givenName}", - "config" : { - "user.attribute" : "firstName", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "given_name", - "Claim JSON Type" : "String" - } - } ] - } ], - "browserSecurityHeaders" : { - "contentSecurityPolicy" : "frame-src 'self'", - "xFrameOptions" : "SAMEORIGIN" - }, - "smtpServer" : { }, - "eventsEnabled" : false, - "eventsListeners" : [ "jboss-logging" ], - "enabledEventTypes" : [ ], - "adminEventsEnabled" : false, - "adminEventsDetailsEnabled" : false, - "identityFederationEnabled" : false, - "internationalizationEnabled" : false, - "supportedLocales" : [ ], - "authenticationFlows" : [ { - "alias" : "registration form", - "description" : "registration form", - "providerId" : "form-flow", - "topLevel" : false, - "builtIn" : true, - "authenticationExecutions" : [ { - "authenticator" : "registration-user-creation", - "autheticatorFlow" : false, - "requirement" : "REQUIRED", - "userSetupAllowed" : false, - "priority" : 20 - }, { - "authenticator" : "registration-profile-action", - "autheticatorFlow" : false, - "requirement" : "REQUIRED", - "userSetupAllowed" : false, - "priority" : 40 - }, { - "authenticator" : "registration-password-action", - "autheticatorFlow" : false, - "requirement" : "REQUIRED", - "userSetupAllowed" : false, - "priority" : 50 - }, { - "authenticator" : "registration-recaptcha-action", - "autheticatorFlow" : false, - "requirement" : "DISABLED", - "userSetupAllowed" : false, - "priority" : 60 - } ] - }, { - "alias" : "direct grant", - "description" : "OpenID Connect Resource Owner Grant", - "providerId" : "basic-flow", - "topLevel" : true, - "builtIn" : true, - "authenticationExecutions" : [ { - "authenticator" : "direct-grant-validate-username", - "autheticatorFlow" : false, - "requirement" : "REQUIRED", - "userSetupAllowed" : false, - "priority" : 10 - }, { - "authenticator" : "direct-grant-validate-password", - "autheticatorFlow" : false, - "requirement" : "REQUIRED", - "userSetupAllowed" : false, - "priority" : 20 - }, { - "authenticator" : "direct-grant-validate-otp", - "autheticatorFlow" : false, - "requirement" : "OPTIONAL", - "userSetupAllowed" : false, - "priority" : 30 - } ] - }, { - "alias" : "reset credentials", - "description" : "Reset credentials for a user if they forgot their password or something", - "providerId" : "basic-flow", - "topLevel" : true, - "builtIn" : true, - "authenticationExecutions" : [ { - "authenticator" : "reset-credentials-choose-user", - "autheticatorFlow" : false, - "requirement" : "REQUIRED", - "userSetupAllowed" : false, - "priority" : 10 - }, { - "authenticator" : "reset-credential-email", - "autheticatorFlow" : false, - "requirement" : "REQUIRED", - "userSetupAllowed" : false, - "priority" : 20 - }, { - "authenticator" : "reset-password", - "autheticatorFlow" : false, - "requirement" : "REQUIRED", - "userSetupAllowed" : false, - "priority" : 30 - }, { - "authenticator" : "reset-otp", - "autheticatorFlow" : false, - "requirement" : "OPTIONAL", - "userSetupAllowed" : false, - "priority" : 40 - } ] - }, { - "alias" : "forms", - "description" : "Username, password, otp and other auth forms.", - "providerId" : "basic-flow", - "topLevel" : false, - "builtIn" : true, - "authenticationExecutions" : [ { - "authenticator" : "auth-username-password-form", - "autheticatorFlow" : false, - "requirement" : "REQUIRED", - "userSetupAllowed" : false, - "priority" : 10 - }, { - "authenticator" : "auth-otp-form", - "autheticatorFlow" : false, - "requirement" : "OPTIONAL", - "userSetupAllowed" : false, - "priority" : 20 - } ] - }, { - "alias" : "clients", - "description" : "Base authentication for clients", - "providerId" : "client-flow", - "topLevel" : true, - "builtIn" : true, - "authenticationExecutions" : [ { - "authenticator" : "client-secret", - "autheticatorFlow" : false, - "requirement" : "ALTERNATIVE", - "userSetupAllowed" : false, - "priority" : 10 - }, { - "authenticator" : "client-jwt", - "autheticatorFlow" : false, - "requirement" : "ALTERNATIVE", - "userSetupAllowed" : false, - "priority" : 20 - } ] - }, { - "alias" : "browser", - "description" : "browser based authentication", - "providerId" : "basic-flow", - "topLevel" : true, - "builtIn" : true, - "authenticationExecutions" : [ { - "authenticator" : "auth-cookie", - "autheticatorFlow" : false, - "requirement" : "ALTERNATIVE", - "userSetupAllowed" : false, - "priority" : 10 - }, { - "authenticator" : "auth-spnego", - "autheticatorFlow" : false, - "requirement" : "DISABLED", - "userSetupAllowed" : false, - "priority" : 20 - }, { - "flowAlias" : "forms", - "autheticatorFlow" : true, - "requirement" : "ALTERNATIVE", - "userSetupAllowed" : false, - "priority" : 30 - } ] - }, { - "alias" : "registration", - "description" : "registration flow", - "providerId" : "basic-flow", - "topLevel" : true, - "builtIn" : true, - "authenticationExecutions" : [ { - "authenticator" : "registration-page-form", - "flowAlias" : "registration form", - "autheticatorFlow" : true, - "requirement" : "REQUIRED", - "userSetupAllowed" : false, - "priority" : 10 - } ] - } ], - "authenticatorConfig" : [ ], - "requiredActions" : [ { - "alias" : "CONFIGURE_TOTP", - "name" : "Configure Totp", - "providerId" : "CONFIGURE_TOTP", - "enabled" : true, - "defaultAction" : false, - "config" : { } - }, { - "alias" : "VERIFY_EMAIL", - "name" : "Verify Email", - "providerId" : "VERIFY_EMAIL", - "enabled" : true, - "defaultAction" : false, - "config" : { } - }, { - "alias" : "terms_and_conditions", - "name" : "Terms and Conditions", - "providerId" : "terms_and_conditions", - "enabled" : false, - "defaultAction" : false, - "config" : { } - }, { - "alias" : "UPDATE_PASSWORD", - "name" : "Update Password", - "providerId" : "UPDATE_PASSWORD", - "enabled" : true, - "defaultAction" : false, - "config" : { } - }, { - "alias" : "UPDATE_PROFILE", - "name" : "Update Profile", - "providerId" : "UPDATE_PROFILE", - "enabled" : true, - "defaultAction" : false, - "config" : { } - } ], - "browserFlow" : "browser", - "registrationFlow" : "registration", - "directGrantFlow" : "direct grant", - "resetCredentialsFlow" : "reset credentials", - "clientAuthenticationFlow" : "clients" -} \ No newline at end of file diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/eap/src/test/java/org/keycloak/testsuite/adapter/example/authorization/EAPDefaultAuthzConfigAdapterTest.java b/testsuite/integration-arquillian/tests/other/adapters/jboss/eap/src/test/java/org/keycloak/testsuite/adapter/example/authorization/EAPDefaultAuthzConfigAdapterTest.java new file mode 100644 index 0000000000..6d63c38935 --- /dev/null +++ b/testsuite/integration-arquillian/tests/other/adapters/jboss/eap/src/test/java/org/keycloak/testsuite/adapter/example/authorization/EAPDefaultAuthzConfigAdapterTest.java @@ -0,0 +1,30 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.keycloak.testsuite.adapter.example.authorization; + +import org.keycloak.testsuite.adapter.example.authorization.AbstractDefaultAuthzConfigAdapterTest; +import org.keycloak.testsuite.arquillian.annotation.AppServerContainer; + +/** + * + * @author tkyjovsk + */ +@AppServerContainer("app-server-eap") +//@AdapterLibsLocationProperty("adapter.libs.wildfly") +public class EAPDefaultAuthzConfigAdapterTest extends AbstractDefaultAuthzConfigAdapterTest { + +} diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/eap/src/test/java/org/keycloak/testsuite/adapter/example/authorization/EAPPhotozExampleAdapterTest.java b/testsuite/integration-arquillian/tests/other/adapters/jboss/eap/src/test/java/org/keycloak/testsuite/adapter/example/authorization/EAPPhotozExampleAdapterTest.java new file mode 100644 index 0000000000..07b9844fc5 --- /dev/null +++ b/testsuite/integration-arquillian/tests/other/adapters/jboss/eap/src/test/java/org/keycloak/testsuite/adapter/example/authorization/EAPPhotozExampleAdapterTest.java @@ -0,0 +1,30 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.keycloak.testsuite.adapter.example.authorization; + +import org.keycloak.testsuite.adapter.example.authorization.AbstractPhotozExampleAdapterTest; +import org.keycloak.testsuite.arquillian.annotation.AppServerContainer; + +/** + * + * @author tkyjovsk + */ +@AppServerContainer("app-server-eap") +//@AdapterLibsLocationProperty("adapter.libs.wildfly") +public class EAPPhotozExampleAdapterTest extends AbstractPhotozExampleAdapterTest { + +} diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/eap/src/test/java/org/keycloak/testsuite/adapter/example/authorization/EAPServletAuthzAdapterTest.java b/testsuite/integration-arquillian/tests/other/adapters/jboss/eap/src/test/java/org/keycloak/testsuite/adapter/example/authorization/EAPServletAuthzAdapterTest.java new file mode 100644 index 0000000000..3c789a4b34 --- /dev/null +++ b/testsuite/integration-arquillian/tests/other/adapters/jboss/eap/src/test/java/org/keycloak/testsuite/adapter/example/authorization/EAPServletAuthzAdapterTest.java @@ -0,0 +1,33 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.keycloak.testsuite.adapter.example.authorization; + +import org.jboss.arquillian.container.test.api.RunAsClient; +import org.keycloak.testsuite.adapter.example.authorization.AbstractDefaultAuthzConfigAdapterTest; +import org.keycloak.testsuite.adapter.example.authorization.AbstractServletAuthzAdapterTest; +import org.keycloak.testsuite.arquillian.annotation.AppServerContainer; + +/** + * + * @author tkyjovsk + */ +@RunAsClient +@AppServerContainer("app-server-eap") +//@AdapterLibsLocationProperty("adapter.libs.wildfly") +public class EAPServletAuthzAdapterTest extends AbstractServletAuthzAdapterTest { + +} diff --git a/testsuite/integration-arquillian/tests/other/adapters/pom.xml b/testsuite/integration-arquillian/tests/other/adapters/pom.xml index 15fc1c4836..5a2a2f31be 100644 --- a/testsuite/integration-arquillian/tests/other/adapters/pom.xml +++ b/testsuite/integration-arquillian/tests/other/adapters/pom.xml @@ -53,7 +53,10 @@ -Xms64m -Xmx512m -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=256m false - + + + + -Dapp.server.base.url=http://localhost:${app.server.http.port} -Dauth.server.base.url=http://localhost:${auth.server.http.port} @@ -63,11 +66,14 @@ -Dauth.server.ssl.required=${auth.server.ssl.required} -Dmy.host.name=localhost -Djava.security.krb5.conf=${project.build.directory}/dependency/kerberos/test-krb5.conf + -Dkie.maven.settings.custom=${settings.path} + -Drepo.url=${repo.url} ${containers.home}/app-server-${app.server} true - + ${main.basedir}/examples + @@ -87,6 +93,23 @@ + + org.commonjava.maven.plugins + directory-maven-plugin + 0.1 + + + directories + + highest-basedir + + initialize + + main.basedir + + + + org.codehaus.mojo xml-maven-plugin @@ -311,20 +334,13 @@ - example-realms + test-apps-realms generate-test-resources unpack - - org.keycloak - keycloak-examples-dist - ${project.version} - zip - **/*realm.json,**/testsaml.json - org.keycloak.testsuite integration-arquillian-test-apps-dist @@ -336,9 +352,35 @@ ${examples.home} true - + - + + + maven-resources-plugin + + + example-realms + generate-test-resources + + copy-resources + + + ${examples.home}/example-realms + true + + + ${examples.basedir} + true + + **/*realm.json + **/testsaml.json + + + + + + + maven-surefire-plugin diff --git a/testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/clients/ClientAuthorizationServicesAvailableTest.java b/testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/clients/ClientAuthorizationServicesAvailableTest.java new file mode 100644 index 0000000000..0dcf08bf9d --- /dev/null +++ b/testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/clients/ClientAuthorizationServicesAvailableTest.java @@ -0,0 +1,65 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.keycloak.testsuite.console.clients; + +import org.jboss.arquillian.graphene.page.Page; +import org.junit.Test; +import org.keycloak.common.Profile; +import org.keycloak.representations.idm.ClientRepresentation; +import org.keycloak.testsuite.ProfileAssume; +import org.keycloak.testsuite.console.page.clients.settings.ClientSettings; +import org.openqa.selenium.By; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.keycloak.testsuite.auth.page.login.Login.OIDC; + +/** + * + * @author Vlastislav Ramik + */ +public class ClientAuthorizationServicesAvailableTest extends AbstractClientTest { + + private ClientRepresentation newClient; + + @Page + private ClientSettings clientSettingsPage; + + @Test + public void authzServicesAvailable() { + ProfileAssume.assumePreview(); + + newClient = createClientRep("oidc-public", OIDC); + createClient(newClient); + assertEquals("oidc-public", clientSettingsPage.form().getClientId()); + + assertTrue(driver.findElement(By.xpath("//*[@for='authorizationServicesEnabled']")).isDisplayed()); + } + + @Test + public void authzServicesUnavailable() throws InterruptedException { + ProfileAssume.assumePreviewDisabled(); + + newClient = createClientRep("oidc-public", OIDC); + createClient(newClient); + assertEquals("oidc-public", clientSettingsPage.form().getClientId()); + + assertFalse(driver.findElement(By.xpath("//*[@for='authorizationServicesEnabled']")).isDisplayed()); + + } +} \ No newline at end of file diff --git a/testsuite/integration-arquillian/tests/other/pom.xml b/testsuite/integration-arquillian/tests/other/pom.xml index 6b0f83c7f1..b2805252e6 100644 --- a/testsuite/integration-arquillian/tests/other/pom.xml +++ b/testsuite/integration-arquillian/tests/other/pom.xml @@ -38,8 +38,9 @@ adapters + sssd - + @@ -65,7 +66,7 @@ - + maven-resources-plugin diff --git a/testsuite/integration-arquillian/tests/other/sssd/README.md b/testsuite/integration-arquillian/tests/other/sssd/README.md new file mode 100644 index 0000000000..03d73a0e82 --- /dev/null +++ b/testsuite/integration-arquillian/tests/other/sssd/README.md @@ -0,0 +1,27 @@ +What is this module about? +------------------------- + +This module containes integration tests for testing the SSSD features of Keycloak. + +Prerequisites +------------- + +To run tests inside this module, one needs to have a linux machine configured as an `IPA` client having sssd + service started with infopipe support. + +How does one run the tests? +-------------------------- + +*All the commands are intended to be run from the root `keycloak` project directory.* + +First build the distribution of keycloak: +`mvn clean install -B -DskipTests -Pdistribution` + +It may fail in the end, but it's not a problem as far as it creates a zip distribution of Keycloak inside +distribution/server-dist/target. + +Then build the integration-arquillian-servers-auth-server-wildfly artifact: +`mvn clean install -B -Pauth-server-wildfly -f testsuite/integration-arquillian/servers/pom.xml` + +And then, finally, it's possible to run the tests: +`mvn test -f testsuite/integration-arquillian/tests/other/sssd/ -Pauth-server-wildfly -Psssd-testing` \ No newline at end of file diff --git a/testsuite/integration-arquillian/tests/other/sssd/pom.xml b/testsuite/integration-arquillian/tests/other/sssd/pom.xml new file mode 100644 index 0000000000..3388822449 --- /dev/null +++ b/testsuite/integration-arquillian/tests/other/sssd/pom.xml @@ -0,0 +1,56 @@ + + + + integration-arquillian-tests-other + org.keycloak.testsuite + 2.2.0-SNAPSHOT + + 4.0.0 + + integration-arquillian-tests-sssd + + SSSD tests + + + **/sssd/**/*Test.java + + + + + + maven-jar-plugin + 2.2 + + + + test-jar + + + + + + + maven-surefire-plugin + + + ${exclude.sssd} + + + + + + + + + + + sssd-testing + + - + + + + + \ No newline at end of file diff --git a/testsuite/integration-arquillian/tests/other/sssd/src/test/java/org/keycloak/testsuite/sssd/SSSDTest.java b/testsuite/integration-arquillian/tests/other/sssd/src/test/java/org/keycloak/testsuite/sssd/SSSDTest.java new file mode 100644 index 0000000000..81ffa988d3 --- /dev/null +++ b/testsuite/integration-arquillian/tests/other/sssd/src/test/java/org/keycloak/testsuite/sssd/SSSDTest.java @@ -0,0 +1,174 @@ +package org.keycloak.testsuite.sssd; + +import org.jboss.arquillian.graphene.page.Page; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.keycloak.representations.idm.GroupRepresentation; +import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.representations.idm.UserFederationProviderRepresentation; +import org.keycloak.representations.idm.UserRepresentation; +import org.keycloak.testsuite.AbstractKeycloakTest; +import org.keycloak.testsuite.Assert; +import org.keycloak.testsuite.AssertEvents; +import org.keycloak.testsuite.pages.AccountPasswordPage; +import org.keycloak.testsuite.pages.AccountUpdateProfilePage; +import org.keycloak.testsuite.pages.LoginPage; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class SSSDTest extends AbstractKeycloakTest { + + private static final String DISPLAY_NAME = "Test user federation"; + private static final String PROVIDER_NAME = "sssd"; + private static final String REALM_NAME = "test"; + + private static final String USERNAME = "emily"; + private static final String PASSWORD = "emily123"; + private static final String DISABLED_USER = "david"; + private static final String DISABLED_USER_PASSWORD = "emily123"; + + private static final String DEFINITELY_NOT_PASSWORD = "not" + PASSWORD; + + private static final String ADMIN_USERNAME = "admin"; + private static final String ADMIN_PASSWORD = "password"; + + @Page + protected LoginPage accountLoginPage; + + @Page + protected AccountPasswordPage changePasswordPage; + + @Page + protected AccountUpdateProfilePage profilePage; + + @Rule + public AssertEvents events = new AssertEvents(this); + + @Override + public void addTestRealms(List testRealms) { + RealmRepresentation realm = new RealmRepresentation(); + + realm.setRealm(REALM_NAME); + realm.setEnabled(true); + + testRealms.add(realm); + } + + @Before + public void createUserFederation() { + UserFederationProviderRepresentation userFederation = new UserFederationProviderRepresentation(); + + Map config = new HashMap<>(); + userFederation.setConfig(config); + + userFederation.setDisplayName(DISPLAY_NAME); + userFederation.setPriority(0); + userFederation.setProviderName(PROVIDER_NAME); + + adminClient.realm(REALM_NAME).userFederation().create(userFederation); + } + + @Test + public void testWrongUser() { + log.debug("Testing wrong password for user " + USERNAME); + + driver.navigate().to(getAccountUrl()); + Assert.assertEquals("Browser should be on login page now", "Log in to " + REALM_NAME, driver.getTitle()); + accountLoginPage.login(USERNAME, DEFINITELY_NOT_PASSWORD); + + Assert.assertEquals("Invalid username or password.", accountLoginPage.getError()); + } + + @Test + public void testDisabledUser() { + log.debug("Testing disabled user " + USERNAME); + + driver.navigate().to(getAccountUrl()); + Assert.assertEquals("Browser should be on login page now", "Log in to " + REALM_NAME, driver.getTitle()); + accountLoginPage.login(DISABLED_USER, DISABLED_USER_PASSWORD); + + Assert.assertEquals("Invalid username or password.", accountLoginPage.getError()); + } + + @Test + public void testAdmin() { + log.debug("Testing wrong password for user " + ADMIN_USERNAME); + + driver.navigate().to(getAccountUrl()); + Assert.assertEquals("Browser should be on login page now", "Log in to " + REALM_NAME, driver.getTitle()); + accountLoginPage.login(ADMIN_USERNAME, ADMIN_PASSWORD); + + Assert.assertEquals("Unexpected error when handling authentication request to identity provider.", accountLoginPage.getInstruction()); + } + + @Test + public void testExistingUserLogIn() { + log.debug("Testing correct password"); + + driver.navigate().to(getAccountUrl()); + Assert.assertEquals("Browser should be on login page now", "Log in to " + REALM_NAME, driver.getTitle()); + accountLoginPage.login(USERNAME, PASSWORD); + Assert.assertEquals("Browser should be on account page now, logged in", "Keycloak Account Management", driver.getTitle()); + + testUserGroups(); + } + + @Test + public void changeReadOnlyProfile() throws Exception { + + profilePage.open(); + accountLoginPage.login(USERNAME, PASSWORD); + + Assert.assertEquals("emily", profilePage.getUsername()); + Assert.assertEquals("Emily", profilePage.getFirstName()); + Assert.assertEquals("Jones", profilePage.getLastName()); + Assert.assertEquals("emily@jones.com", profilePage.getEmail()); + + profilePage.updateProfile("New first", "New last", "new@email.com"); + + Assert.assertEquals("You can't update your account as it is read only.", profilePage.getError()); + } + + @Test + public void changeReadOnlyPassword() { + changePasswordPage.open(); + accountLoginPage.login(USERNAME, PASSWORD); + + changePasswordPage.changePassword(PASSWORD, "new-password", "new-password"); + Assert.assertEquals("You can't update your password as your account is read only.", profilePage.getError()); + } + + + private void testUserGroups() { + log.debug("Testing user groups"); + + List users = adminClient.realm(REALM_NAME).users().search(USERNAME, 0, 1); + + Assert.assertTrue("There must be at least one user", users.size() > 0); + Assert.assertEquals("Exactly our test user", USERNAME, users.get(0).getUsername()); + + List groups = adminClient.realm(REALM_NAME).users().get(users.get(0).getId()).groups(); + + Assert.assertEquals("User must have exactly two groups", 2, groups.size()); + boolean wrongGroup = false; + for (GroupRepresentation group : groups) { + if (!group.getName().equalsIgnoreCase("ipausers") && !group.getName().equalsIgnoreCase("testgroup")) { + wrongGroup = true; + break; + } + } + + Assert.assertFalse("There exists some wrong group", wrongGroup); + } + + private String getAccountUrl() { + return getAuthRoot() + "/auth/realms/" + REALM_NAME + "/account"; + } + + private String getAuthRoot() { + return suiteContext.getAuthServerInfo().getContextRoot().toString(); + } +} diff --git a/testsuite/integration-arquillian/tests/pom.xml b/testsuite/integration-arquillian/tests/pom.xml index b2ee848b26..3e97fc1aac 100755 --- a/testsuite/integration-arquillian/tests/pom.xml +++ b/testsuite/integration-arquillian/tests/pom.xml @@ -37,7 +37,7 @@ base other - + undertow true @@ -54,7 +54,6 @@ 10090 10099 false - 60 -Xms64m -Xmx512m -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=256m serverConfig org.jboss.as.arquillian.container.managed.ManagedDeployableContainer @@ -76,6 +75,7 @@ ${project.build.directory}/dependency/test-constants.properties + false @@ -116,6 +116,7 @@ copy-resources + ${skip.add.user.json} ${auth.server.config.dir} @@ -178,7 +179,7 @@ listener - org.keycloak.testsuite.util.TestEventsLogger + org.keycloak.testsuite.util.TestEventsLogger,org.keycloak.testsuite.util.junit.AggregateResultsReporter @@ -402,7 +403,7 @@ - + migration @@ -411,30 +412,8 @@ migrated.auth.server.version - - - - - - - maven-enforcer-plugin - - - enforce-properties - - enforce - - - - - migrated.auth.server.version - - - - - - maven-dependency-plugin @@ -448,7 +427,7 @@ org.keycloak.testsuite - ${migrated.auth.server.jboss.artifactId} + integration-arquillian-migration-server ${project.version} zip @@ -464,158 +443,16 @@ ${migrated.auth.server.version} + true + ${containers.home}/keycloak-${migrated.auth.server.version} + src/test/resources/migration-test/migration-realm-${migrated.auth.server.version}.json - - - migration-kc16 - - - migrated.auth.server.version - 1.6.1.Final - - - - integration-arquillian-server-wildfly-kc16 - - - - - - maven-surefire-plugin - - - true - ${containers.home}/keycloak-${migrated.auth.server.version} - src/test/resources/migration-test/migration-realm-16.json - - - - - - - - - - migration-kc15 - - - migrated.auth.server.version - 1.5.1.Final - - - - integration-arquillian-server-wildfly-kc15 - - - - - - maven-surefire-plugin - - - true - ${containers.home}/keycloak-${migrated.auth.server.version} - src/test/resources/migration-test/migration-realm-15.json - - - - - - - - - - migration-kc14 - - - migrated.auth.server.version - 1.4.0.Final - - - - integration-arquillian-server-wildfly-kc14 - - - - - - maven-surefire-plugin - - - true - ${containers.home}/keycloak-${migrated.auth.server.version} - - - - - - - - - - migration-kc13 - - - migrated.auth.server.version - 1.3.1.Final - - - - integration-arquillian-server-wildfly-kc13 - - - - - - maven-surefire-plugin - - - true - ${containers.home}/keycloak-${migrated.auth.server.version} - - - - - - - - - - migration-kc12 - - - migrated.auth.server.version - 1.2.0.Final - - - - integration-arquillian-server-wildfly-kc12 - - - - - - maven-surefire-plugin - - - true - ${containers.home}/keycloak-${migrated.auth.server.version} - - - - - - - - - - - + no-account @@ -645,6 +482,13 @@ + + + org.keycloak.testsuite + integration-arquillian-test-utils + ${project.version} + + junit diff --git a/testsuite/integration/pom.xml b/testsuite/integration/pom.xml index aca1d78278..1e92f9b90e 100755 --- a/testsuite/integration/pom.xml +++ b/testsuite/integration/pom.xml @@ -166,6 +166,14 @@ org.keycloak federation-properties-example + + + + org.keycloak.testsuite + integration-arquillian-testsuite-providers + ${project.version} + + org.jboss.logging jboss-logging @@ -502,6 +510,51 @@ + + mongodb-server + + + localhost + 27018 + keycloak + 127.0.0.1 + + + + + + + + com.github.joelittlejohn.embedmongo + embedmongo-maven-plugin + + + start-mongodb + pre-integration-test + + start + + + ${keycloak.connectionsMongo.port} + file + ${project.build.directory}/mongodb.log + ${keycloak.connectionsMongo.bindIp} + + + + stop-mongodb + post-integration-test + + stop + + + + + + + + + clean-jpa diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/DummyUserFederationProvider.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/DummyUserFederationProvider.java deleted file mode 100755 index 0669da47db..0000000000 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/DummyUserFederationProvider.java +++ /dev/null @@ -1,150 +0,0 @@ -/* - * Copyright 2016 Red Hat, Inc. and/or its affiliates - * and other contributors as indicated by the @author tags. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.keycloak.testsuite; - -import org.keycloak.models.CredentialValidationOutput; -import org.keycloak.models.GroupModel; -import org.keycloak.models.RealmModel; -import org.keycloak.models.RoleModel; -import org.keycloak.models.UserCredentialModel; -import org.keycloak.models.UserFederationProvider; -import org.keycloak.models.UserModel; - -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -/** - * @author Bill Burke - * @version $Revision: 1 $ - */ -public class DummyUserFederationProvider implements UserFederationProvider { - - private final Map users; - - public DummyUserFederationProvider(Map users) { - this.users = users; - } - - @Override - public UserModel validateAndProxy(RealmModel realm, UserModel local) { - return local; - } - - @Override - public boolean synchronizeRegistrations() { - return true; - } - - @Override - public UserModel register(RealmModel realm, UserModel user) { - users.put(user.getUsername(), user); - return user; - } - - @Override - public boolean removeUser(RealmModel realm, UserModel user) { - return users.remove(user.getUsername()) != null; - } - - @Override - public UserModel getUserByUsername(RealmModel realm, String username) { - return users.get(username); - } - - @Override - public UserModel getUserByEmail(RealmModel realm, String email) { - return null; - } - - @Override - public List searchByAttributes(Map attributes, RealmModel realm, int maxResults) { - return Collections.emptyList(); - } - - @Override - public List getGroupMembers(RealmModel realm, GroupModel group, int firstResult, int maxResults) { - return Collections.emptyList(); - } - - @Override - public void preRemove(RealmModel realm) { - - } - - @Override - public void preRemove(RealmModel realm, RoleModel role) { - - } - - @Override - public void preRemove(RealmModel realm, GroupModel group) { - - } - - @Override - public boolean isValid(RealmModel realm, UserModel local) { - String username = local.getUsername(); - return users.containsKey(username); - } - - @Override - public Set getSupportedCredentialTypes(UserModel user) { - // Just user "test-user" is able to validate password with this federationProvider - if (user.getUsername().equals("test-user")) { - return Collections.singleton(UserCredentialModel.PASSWORD); - } else { - return Collections.emptySet(); - } - } - - @Override - public Set getSupportedCredentialTypes() { - return Collections.singleton(UserCredentialModel.PASSWORD); - } - - @Override - public boolean validCredentials(RealmModel realm, UserModel user, List input) { - if (user.getUsername().equals("test-user") && input.size() == 1) { - UserCredentialModel password = input.get(0); - if (password.getType().equals(UserCredentialModel.PASSWORD)) { - return "secret".equals(password.getValue()); - } - } - return false; - } - - @Override - public boolean validCredentials(RealmModel realm, UserModel user, UserCredentialModel... input) { - return validCredentials(realm, user, Arrays.asList(input)); - } - - @Override - public CredentialValidationOutput validCredentials(RealmModel realm, UserCredentialModel credential) { - return CredentialValidationOutput.failed(); - } - - @Override - public void close() { - - } -} diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/DummyUserFederationProviderFactory.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/DummyUserFederationProviderFactory.java deleted file mode 100755 index 4b49499137..0000000000 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/DummyUserFederationProviderFactory.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Copyright 2016 Red Hat, Inc. and/or its affiliates - * and other contributors as indicated by the @author tags. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.keycloak.testsuite; - -import org.jboss.logging.Logger; -import org.keycloak.Config; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.KeycloakSessionFactory; -import org.keycloak.models.UserFederationProvider; -import org.keycloak.models.UserFederationProviderFactory; -import org.keycloak.models.UserFederationProviderModel; -import org.keycloak.models.UserFederationSyncResult; -import org.keycloak.models.UserModel; -import org.keycloak.provider.ConfiguredProvider; -import org.keycloak.provider.ProviderConfigProperty; - -import java.util.*; -import java.util.concurrent.atomic.AtomicInteger; - -/** - * @author Bill Burke - * @version $Revision: 1 $ - */ -public class DummyUserFederationProviderFactory implements UserFederationProviderFactory, ConfiguredProvider { - - private static final Logger logger = Logger.getLogger(DummyUserFederationProviderFactory.class); - public static final String PROVIDER_NAME = "dummy"; - - private AtomicInteger fullSyncCounter = new AtomicInteger(); - private AtomicInteger changedSyncCounter = new AtomicInteger(); - - private Map users = new HashMap(); - - @Override - public UserFederationProvider getInstance(KeycloakSession session, UserFederationProviderModel model) { - return new DummyUserFederationProvider(users); - } - - @Override - public Set getConfigurationOptions() { - Set list = new HashSet(); - list.add("important.config"); - return list; - } - - @Override - public UserFederationProvider create(KeycloakSession session) { - return new DummyUserFederationProvider(users); - } - - @Override - public void init(Config.Scope config) { - - } - - @Override - public void postInit(KeycloakSessionFactory factory) { - - } - - @Override - public void close() { - - } - - @Override - public String getId() { - return PROVIDER_NAME; - } - - @Override - public UserFederationSyncResult syncAllUsers(KeycloakSessionFactory sessionFactory, String realmId, UserFederationProviderModel model) { - logger.info("syncAllUsers invoked"); - fullSyncCounter.incrementAndGet(); - return UserFederationSyncResult.empty(); - } - - @Override - public UserFederationSyncResult syncChangedUsers(KeycloakSessionFactory sessionFactory, String realmId, UserFederationProviderModel model, Date lastSync) { - logger.info("syncChangedUsers invoked"); - changedSyncCounter.incrementAndGet(); - return UserFederationSyncResult.empty(); - } - - public int getFullSyncCounter() { - return fullSyncCounter.get(); - } - - public int getChangedSyncCounter() { - return changedSyncCounter.get(); - } - - @Override - public String getHelpText() { - return "Dummy User Federation Provider Help Text"; - } - - @Override - public List getConfigProperties() { - - ProviderConfigProperty prop1 = new ProviderConfigProperty(); - prop1.setName("prop1"); - prop1.setLabel("Prop1"); - prop1.setDefaultValue("prop1Default"); - prop1.setHelpText("Prop1 HelpText"); - prop1.setType(ProviderConfigProperty.STRING_TYPE); - - ProviderConfigProperty prop2 = new ProviderConfigProperty(); - prop2.setName("prop2"); - prop2.setLabel("Prop2"); - prop2.setDefaultValue("true"); - prop2.setHelpText("Prop2 HelpText"); - prop2.setType(ProviderConfigProperty.BOOLEAN_TYPE); - - return Arrays.asList(prop1, prop2); - } -} diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractFirstBrokerLoginTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractFirstBrokerLoginTest.java index 3b1b440920..67b82bb046 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractFirstBrokerLoginTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractFirstBrokerLoginTest.java @@ -287,6 +287,10 @@ public abstract class AbstractFirstBrokerLoginTest extends AbstractIdentityProvi // authenticated and redirected to app. User is linked with identity provider assertFederatedUser("pedroigor", "psilva@redhat.com", "pedroigor"); + + // Assert user's email is verified now + UserModel user = getFederatedUser(); + Assert.assertTrue(user.isEmailVerified()); } diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractKeycloakIdentityProviderTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractKeycloakIdentityProviderTest.java index 7683d0c0f4..f6a85e46d1 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractKeycloakIdentityProviderTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractKeycloakIdentityProviderTest.java @@ -45,8 +45,8 @@ import org.keycloak.models.UserFederationProviderModel; import org.keycloak.models.UserModel; import org.keycloak.representations.idm.IdentityProviderRepresentation; import org.keycloak.services.Urls; -import org.keycloak.testsuite.DummyUserFederationProviderFactory; import org.keycloak.testsuite.broker.util.UserSessionStatusServlet; +import org.keycloak.testsuite.federation.DummyUserFederationProviderFactory; import org.openqa.selenium.By; import org.openqa.selenium.NoSuchElementException; diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/IdentityProviderHintTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/IdentityProviderHintTest.java index 7c1f47beb0..36c291f02b 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/IdentityProviderHintTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/IdentityProviderHintTest.java @@ -20,19 +20,19 @@ package org.keycloak.testsuite.broker; import org.junit.ClassRule; import org.junit.Rule; import org.junit.Test; +import org.keycloak.admin.client.resource.RealmResource; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; +import org.keycloak.representations.idm.AuthenticationExecutionInfoRepresentation; import org.keycloak.services.managers.RealmManager; +import org.keycloak.testsuite.KeycloakServer; import org.keycloak.testsuite.pages.LoginPage; import org.keycloak.testsuite.pages.OAuthGrantPage; import org.keycloak.testsuite.rule.AbstractKeycloakRule; import org.keycloak.testsuite.rule.WebResource; import org.keycloak.testsuite.rule.WebRule; -import org.keycloak.testsuite.KeycloakServer; -import org.openqa.selenium.By; import org.openqa.selenium.WebDriver; -import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; /** @@ -94,6 +94,16 @@ public class IdentityProviderHintTest { assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth")); - assertEquals("Could not find an identity provider with the identifier.", this.driver.findElement(By.className("instruction")).getText()); + System.out.println(driver.getPageSource()); + assertTrue(driver.getTitle().equals("Log in to realm-with-broker")); + } + + private AuthenticationExecutionInfoRepresentation findExecution(RealmResource realm) { + for (AuthenticationExecutionInfoRepresentation e : realm.flows().getExecutions("browser")) { + if (e.getProviderId().equals("identity-provider-redirector")) { + return e; + } + } + return null; } } diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/sync/SyncDummyUserFederationProviderFactory.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/sync/SyncDummyUserFederationProviderFactory.java index 5d327f4bbd..3831787aa1 100644 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/sync/SyncDummyUserFederationProviderFactory.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/sync/SyncDummyUserFederationProviderFactory.java @@ -32,7 +32,7 @@ import org.keycloak.models.UserFederationProviderModel; import org.keycloak.models.UserFederationSyncResult; import org.keycloak.models.UserModel; import org.keycloak.models.utils.KeycloakModelUtils; -import org.keycloak.testsuite.DummyUserFederationProviderFactory; +import org.keycloak.testsuite.federation.DummyUserFederationProviderFactory; /** * @author Marek Posolda diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/sync/SyncFederationTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/sync/SyncFederationTest.java index 84a901a171..efa688c5a2 100644 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/sync/SyncFederationTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/sync/SyncFederationTest.java @@ -36,7 +36,7 @@ import org.keycloak.models.UserFederationProviderModel; import org.keycloak.models.UserFederationSyncResult; import org.keycloak.services.managers.RealmManager; import org.keycloak.services.managers.UsersSyncManager; -import org.keycloak.testsuite.DummyUserFederationProviderFactory; +import org.keycloak.testsuite.federation.DummyUserFederationProviderFactory; import org.keycloak.testsuite.rule.KeycloakRule; import org.keycloak.timer.TimerProvider; diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ConcurrentTransactionsTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ConcurrentTransactionsTest.java index b3ad0c4b5b..2fb2aaf7eb 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ConcurrentTransactionsTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ConcurrentTransactionsTest.java @@ -17,6 +17,7 @@ package org.keycloak.testsuite.model; +import java.util.Arrays; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicReference; @@ -151,14 +152,17 @@ public class ConcurrentTransactionsTest extends AbstractModelTest { } - // KEYCLOAK-3296 + // KEYCLOAK-3296 , KEYCLOAK-3494 @Test public void removeUserAttribute() throws Exception { RealmModel realm = realmManager.createRealm("original"); KeycloakSession session = realmManager.getSession(); - UserModel user = session.users().addUser(realm, "john"); - user.setSingleAttribute("foo", "val1"); + UserModel john = session.users().addUser(realm, "john"); + john.setSingleAttribute("foo", "val1"); + + UserModel john2 = session.users().addUser(realm, "john2"); + john2.setAttribute("foo", Arrays.asList("val1", "val2")); final KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory(); commit(); @@ -182,12 +186,18 @@ public class ConcurrentTransactionsTest extends AbstractModelTest { UserModel john = session.users().getUserByUsername("john", realm); String attrVal = john.getFirstAttribute("foo"); + UserModel john2 = session.users().getUserByUsername("john2", realm); + String attrVal2 = john2.getFirstAttribute("foo"); + // Wait until it's read in both threads readAttrLatch.countDown(); readAttrLatch.await(); - // Remove user attribute in both threads + // KEYCLOAK-3296 : Remove user attribute in both threads john.removeAttribute("foo"); + + // KEYCLOAK-3494 : Set single attribute in both threads + john2.setSingleAttribute("foo", "bar"); } catch (Exception e) { throw new RuntimeException(e); } diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserModelTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserModelTest.java index 4b3f78ef12..e2af2416d3 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserModelTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserModelTest.java @@ -211,6 +211,31 @@ public class UserModelTest extends AbstractModelTest { Assert.assertEquals("val23", attrVals.get(0)); } + // KEYCLOAK-3494 + @Test + public void testUpdateUserAttribute() throws Exception { + RealmModel realm = realmManager.createRealm("original"); + UserModel user = session.users().addUser(realm, "user"); + + user.setSingleAttribute("key1", "value1"); + + commit(); + + realm = realmManager.getRealmByName("original"); + user = session.users().getUserByUsername("user", realm); + + // Update attribute + List attrVals = new ArrayList<>(Arrays.asList( "val2" )); + user.setAttribute("key1", attrVals); + Map> allAttrVals = user.getAttributes(); + + // Ensure same transaction is able to see updated value + Assert.assertEquals(1, allAttrVals.size()); + Assert.assertEquals(allAttrVals.get("key1"), Arrays.asList("val2")); + + commit(); + } + @Test public void testSearchByString() { RealmModel realm = realmManager.createRealm("original"); diff --git a/testsuite/integration/src/test/resources/META-INF/services/org.keycloak.models.UserFederationProviderFactory b/testsuite/integration/src/test/resources/META-INF/services/org.keycloak.models.UserFederationProviderFactory index 1b4de7323d..d4a16d36c8 100755 --- a/testsuite/integration/src/test/resources/META-INF/services/org.keycloak.models.UserFederationProviderFactory +++ b/testsuite/integration/src/test/resources/META-INF/services/org.keycloak.models.UserFederationProviderFactory @@ -15,5 +15,4 @@ # limitations under the License. # -org.keycloak.testsuite.DummyUserFederationProviderFactory org.keycloak.testsuite.federation.sync.SyncDummyUserFederationProviderFactory \ No newline at end of file diff --git a/testsuite/integration/src/test/resources/log4j.properties b/testsuite/integration/src/test/resources/log4j.properties index 5d5369c20b..f0ff6ac6e3 100755 --- a/testsuite/integration/src/test/resources/log4j.properties +++ b/testsuite/integration/src/test/resources/log4j.properties @@ -74,4 +74,7 @@ log4j.logger.org.apache.directory.server.core=warn log4j.logger.org.apache.directory.server.ldap.LdapProtocolHandler=error # Enable to view HttpClient connection pool activity -#log4j.logger.org.apache.http.impl.conn=debug \ No newline at end of file +#log4j.logger.org.apache.http.impl.conn=debug + +# Enable to view details from identity provider authenticator +# log4j.logger.org.keycloak.authentication.authenticators.browser.IdentityProviderAuthenticator=trace \ No newline at end of file diff --git a/themes/src/main/resources/theme/base/account/messages/messages_en.properties b/themes/src/main/resources/theme/base/account/messages/messages_en.properties index bdcf43aedb..1f4200808c 100755 --- a/themes/src/main/resources/theme/base/account/messages/messages_en.properties +++ b/themes/src/main/resources/theme/base/account/messages/messages_en.properties @@ -158,5 +158,7 @@ locale_es=Espa\u00F1ol locale_fr=Fran\u00e7ais locale_it=Italian locale_ja=\u65E5\u672C\u8A9E +locale_no=Norsk +locale_lt=Lietuvi\u0173 locale_pt-BR=Portugu\u00EAs (Brasil) locale_ru=\u0420\u0443\u0441\u0441\u043A\u0438\u0439 \ No newline at end of file diff --git a/themes/src/main/resources/theme/base/account/messages/messages_lt.properties b/themes/src/main/resources/theme/base/account/messages/messages_lt.properties new file mode 100644 index 0000000000..f9edf13ecf --- /dev/null +++ b/themes/src/main/resources/theme/base/account/messages/messages_lt.properties @@ -0,0 +1,153 @@ +doSave=Saugoti +doCancel=At\u0161aukti + +doLogOutAllSessions=Atjungti visas sesijas +doRemove=\u0160alinti +doAdd=Prid\u0117ti +doSignOut=Atsijungti + +editAccountHtmlTitle=Redaguoti paskyr\u0105 +federatedIdentitiesHtmlTitle=Susietos paskyros +accountLogHtmlTitle=Paskyros \u017Eurnalas +changePasswordHtmlTitle=Keisti slapta\u017Eod\u012F +sessionsHtmlTitle=Prisijungimo sesijos +accountManagementTitle=Keycloak Naudotoj\u0173 Administravimas +authenticatorTitle=Autentifikatorius +applicationsHtmlTitle=Programos + +authenticatorCode=Vienkartinis kodas +email=El. pa\u0161tas +firstName=Vardas +givenName=Pavard\u0117 +fullName=Pilnas vardas +lastName=Pavard\u0117 +familyName=Pavard\u0117 +password=Slapta\u017Eodis +passwordConfirm=Pakartotas slapta\u017Eodis +passwordNew=Naujas slapta\u017Eodis +username=Naudotojo vardas +address=Adresas +street=Gatv\u0117 +locality=Miestas arba vietov\u0117 +region=Rajonas +postal_code=Pa\u0161to kodas +country=\u0160alis +emailVerified=El. pa\u0161to adresas patvirtintas +gssDelegationCredential=GSS prisijungimo duomen\u0173 delegavimas + +role_admin=Administratorius +role_realm-admin=Srities administravimas +role_create-realm=Kurti srit\u012F +role_view-realm=Per\u017Ei\u016Br\u0117ti srit\u012F +role_view-users=Per\u017Ei\u016Br\u0117ti naudotojus +role_view-applications=Per\u017Ei\u016Br\u0117ti programas +role_view-clients=Per\u017Ei\u016Br\u0117ti klientines programas +role_view-events=Per\u017Ei\u016Br\u0117ti \u012Fvyki\u0173 \u017Eurnal\u0105 +role_view-identity-providers=Per\u017Ei\u016Br\u0117ti tapatyb\u0117s teik\u0117jus +role_manage-realm=Valdyti sritis +role_manage-users=Valdyti naudotojus +role_manage-applications=Valdyti programas +role_manage-identity-providers=Valdyti tapatyb\u0117s teik\u0117jus +role_manage-clients=Valdyti programas +role_manage-events=Valdyti \u012Fvykius +role_view-profile=Per\u017Ei\u016Br\u0117ti paskyr\u0105 +role_manage-account=Valdyti paskyr\u0105 +role_read-token=Skaityti prieigos rak\u0161\u0105 +role_offline-access=Darbas neprisijungus +role_uma_authorization=\u012Egauti UMA autorizavimo teises +client_account=Paskyra +client_security-admin-console=Saugumo administravimo konsol\u0117 +client_admin-cli=Administravimo CLI +client_realm-management=Srities valdymas +client_broker=Tarpininkas + + +requiredFields=Privalomi laukai +allFieldsRequired=Visi laukai yra privalomi + +backToApplication=« Gr\u012F\u017Eti \u012F program\u0105 +backTo=Atgal \u012F {0} + +date=Data +event=\u012Evykis +ip=IP +client=Klientas +clients=Klientai +details=Detaliau +started=Suk\u016Brimo laikas +lastAccess=V\u0117liausia prieiga +expires=Galioja iki +applications=Programos + +account=Paskyra +federatedIdentity=Susieta tapatyb\u0117 +authenticator=Autentifikatorius +sessions=Sesijos +log=\u012Evykiai + +application=Programa +availablePermissions=Galimos teis\u0117s +grantedPermissions=\u012Egalintos teis\u0117s +grantedPersonalInfo=\u012Egalinta asmenin\u0117 informacija +additionalGrants=Papildomi \u012Fgaliojimai +action=Veiksmas +inResource=yra +fullAccess=Pilna prieiga +offlineToken=Re\u017Eimo neprisijungus raktas (token) +revoke=At\u0161aukti \u012Fgaliojim\u0105 + +configureAuthenticators=Sukonfig\u016Bruotas autentifikatorius +mobile=Mobilus +totpStep1=\u012Ediekite FreeOTP arba Google Authenticator savo \u012Frenginyje. Program\u0117l\u0117s prieinamos Google Play ir Apple App Store. +totpStep2=Atidarykite program\u0117l\u0119 ir nuskenuokite barkod\u0105 arba \u012Fveskite kod\u0105. +totpStep3=\u012Eveskite program\u0117l\u0117je sugeneruot\u0105 vien\u0105 kart\u0105 galiojant\u012F kod\u0105 ir paspauskite Saugoti nor\u0117dami prisijungti. + +missingUsernameMessage=Pra\u0161ome \u012Fvesti naudotojo vard\u0105. +missingFirstNameMessage=Pra\u0161ome \u012Fvesti vard\u0105. +invalidEmailMessage=Neteisingas el. pa\u0161to adresas. +missingLastNameMessage=Pra\u0161ome \u012Fvesti pavard\u0119. +missingEmailMessage=Pra\u0161ome \u012Fvesti el. pa\u0161to adres\u0105. +missingPasswordMessage=Pra\u0161ome \u012Fvesti slapta\u017Eod\u012F. +notMatchPasswordMessage=Slapta\u017Eod\u017Eiai nesutampa. + +missingTotpMessage=Pra\u0161ome \u012Fvesti autentifikacijos kod\u0105. +invalidPasswordExistingMessage=Neteisingas dabartinis slapta\u017Eodis. +invalidPasswordConfirmMessage=Pakartotas slapta\u017Eodis nesutampa. +invalidTotpMessage=Neteisingas autentifikacijos kodas. + +usernameExistsMessage=Toks naudotojas jau egzistuoja. +emailExistsMessage=El. pa\u0161to adresas jau egzistuoja. + +readOnlyUserMessage=Tik skaitymui sukonfig\u016Bruotos paskyros duomen\u0173 atnaujinti neleid\u017Eiama. +readOnlyPasswordMessage=Tik skaitymui sukonfig\u016Bruotos paskyros slapta\u017Eod\u017Eio atnaujinti neleid\u017Eiama. + +successTotpMessage=Mobilus autentifikatorius sukonfig\u016Bruotas. +successTotpRemovedMessage=Mobilus autentifikatorius pa\u0161alintas. + +successGrantRevokedMessage=\u012Egalinimas pa\u0161alintas s\u0117kmingai. + +accountUpdatedMessage=J\u016Bs\u0173 paskyros duomenys s\u0117kmingai atnaujinti. +accountPasswordUpdatedMessage=J\u016Bs\u0173 paskyros slapta\u017Eodis pakeistas. + +missingIdentityProviderMessage=Nenurodytas tapatyb\u0117s teik\u0117jas. +invalidFederatedIdentityActionMessage=Neteisingas arba ne\u017Einomas veiksmas. +identityProviderNotFoundMessage=Nurodytas tapatyb\u0117s teik\u0117jas nerastas. +federatedIdentityLinkNotActiveMessage=Nurodyta susieta tapatyb\u0117 neaktyvi. +federatedIdentityRemovingLastProviderMessage=J\u016Bs negalite pa\u0161alinti paskutinio tapatyb\u0117s teik\u0117jo s\u0105sajos, nes J\u016Bs neturite nusistat\u0119 paskyros slapta\u017Eod\u017Eio. +identityProviderRedirectErrorMessage=Klaida nukreipiant \u012F tapatyb\u0117s teik\u0117jo puslap\u012F. +identityProviderRemovedMessage=Tapatyb\u0117s teik\u0117jas s\u0117kmingai pa\u0161alintas. +identityProviderAlreadyLinkedMessage=Susieta tapatyb\u0117 i\u0161 {0} jau susieta su kita paskyra. +staleCodeAccountMessage=Puslapio galiojimas baig\u0117si. Bandykite dar kart\u0105. +consentDenied=Prieiga draud\u017Eiama. + +accountDisabledMessage=Paskyros galiojimas sustabdytas, kreipkit\u0117s \u012F administratori\u0173. + +accountTemporarilyDisabledMessage=Paskyros galiojimas laikinai sustabdytas. Kreipkit\u0117s \u012F administratori\u0173 arba pabandykite v\u0117liau. +invalidPasswordMinLengthMessage=Per trumpas slapta\u017Eodis: ma\u017Eiausias ilgis {0}. +invalidPasswordMinLowerCaseCharsMessage=Neteisingas slapta\u017Eodis: privaloma \u012Fvesti {0} ma\u017E\u0105j\u0105 raid\u0119. +invalidPasswordMinDigitsMessage=Neteisingas slapta\u017Eodis: privaloma \u012Fvesti {0} skaitmen\u012F. +invalidPasswordMinUpperCaseCharsMessage=Neteisingas slapta\u017Eodis: privaloma \u012Fvesti {0} did\u017Ei\u0105j\u0105 raid\u0119. +invalidPasswordMinSpecialCharsMessage=Neteisingas slapta\u017Eodis: privaloma \u012Fvesti {0} special\u0173 simbol\u012F. +invalidPasswordNotUsernameMessage=Neteisingas slapta\u017Eodis: slapta\u017Eodis negali sutapti su naudotojo vardu. +invalidPasswordRegexPatternMessage=Neteisingas slapta\u017Eodis: slapta\u017Eodis netenkina regex taisykl\u0117s(i\u0173). +invalidPasswordHistoryMessage=Neteisingas slapta\u017Eodis: slapta\u017Eodis negali sutapti su prie\u0161 tai buvusiais {0} slapta\u017Eod\u017Eiais. \ No newline at end of file diff --git a/themes/src/main/resources/theme/base/account/messages/messages_no.properties b/themes/src/main/resources/theme/base/account/messages/messages_no.properties new file mode 100644 index 0000000000..3cae31ef16 --- /dev/null +++ b/themes/src/main/resources/theme/base/account/messages/messages_no.properties @@ -0,0 +1,163 @@ +doSave=Lagre +doCancel=Avbryt +doLogOutAllSessions=Logg ut av alle sesjoner +doRemove=Fjern +doAdd=Legg til +doSignOut=Logg ut + +editAccountHtmlTitle=Rediger konto +federatedIdentitiesHtmlTitle=Federerte identiteter +accountLogHtmlTitle=Kontologg +changePasswordHtmlTitle=Endre passord +sessionsHtmlTitle=Sesjoner +accountManagementTitle=Keycloak kontoadministrasjon +authenticatorTitle=Autentikator +applicationsHtmlTitle=Applikasjoner + +authenticatorCode=Engangskode +email=E-post +firstName=Fornavn +givenName=Fornavn +fullName=Fullt navn +lastName=Etternavn +familyName=Etternavn +password=Passord +passwordConfirm=Bekreftelse +passwordNew=Nytt passord +username=Brukernavn +address=Adresse +street=Gate-/veinavn + husnummer +locality=By +region=Fylke +postal_code=Postnummer +country=Land +emailVerified=E-post bekreftet +gssDelegationCredential=GSS legitimasjonsdelegering + +role_admin=Administrator +role_realm-admin=Administrator for sikkerhetsdomene +role_create-realm=Opprette sikkerhetsdomene +role_view-realm=Se sikkerhetsdomene +role_view-users=Se brukere +role_view-applications=Se applikasjoner +role_view-clients=Se klienter +role_view-events=Se hendelser +role_view-identity-providers=Se identitetsleverand\u00F8rer +role_manage-realm=Administrere sikkerhetsdomene +role_manage-users=Administrere brukere +role_manage-applications=Administrere applikasjoner +role_manage-identity-providers=Administrere identitetsleverand\u00F8rer +role_manage-clients=Administrere klienter +role_manage-events=Administrere hendelser +role_view-profile=Se profil +role_manage-account=Administrere konto +role_read-token=Lese token +role_offline-access=Frakoblet tilgang +role_uma_authorization=Skaffe tillatelser +client_account=Konto +client_security-admin-console=Sikkerhetsadministrasjonskonsoll +client_admin-cli=Kommandolinje-grensesnitt for administrator +client_realm-management=Sikkerhetsdomene-administrasjon +client_broker=Broker + + +requiredFields=Obligatoriske felt +allFieldsRequired=Alle felt m\u00E5 fylles ut + +backToApplication=« Tilbake til applikasjonen +backTo=Tilbake til {0} + +date=Dato +event=Hendelse +ip=IP +client=Klient +clients=Klienter +details=Detaljer +started=Startet +lastAccess=Sist benyttet +expires=Utl\u00F8per +applications=Applikasjoner + +account=Konto +federatedIdentity=Federert identitet +authenticator=Autentikator +sessions=Sesjoner +log=Logg + +application=Applikasjon +availablePermissions=Tilgjengelige rettigheter +grantedPermissions=Innvilgede rettigheter +grantedPersonalInfo=Innvilget personlig informasjon +additionalGrants=Ekstra rettigheter +action=Handling +inResource=i +fullAccess=Full tilgang +offlineToken=Offline token +revoke=Opphev rettighet + +configureAuthenticators=Konfigurerte autentikatorer +mobile=Mobiltelefon +totpStep1=Installer FreeOTP eller Google Authenticator p\u00E5 din enhet. Begge applikasjoner er tilgjengelige p\u00E5 Google Play og Apple App Store. +totpStep2=\u00C5pne applikasjonen og skann strekkoden eller skriv inn koden. +totpStep3=Skriv inn engangskoden gitt av applikasjonen og klikk Lagre for \u00E5 fullf\u00F8re. + +missingUsernameMessage=Vennligst oppgi brukernavn. +missingFirstNameMessage=Vennligst oppgi fornavn. +invalidEmailMessage=Ugyldig e-postadresse. +missingLastNameMessage=Vennligst oppgi etternavn. +missingEmailMessage=Vennligst oppgi e-postadresse. +missingPasswordMessage=Vennligst oppgi passord. +notMatchPasswordMessage=Passordene er ikke like. + +missingTotpMessage=Vennligst oppgi engangskode. +invalidPasswordExistingMessage=Ugyldig eksisterende passord. +invalidPasswordConfirmMessage=Passordene er ikke like. +invalidTotpMessage=Ugyldig engangskode. + +usernameExistsMessage=Brukernavnet finnes allerede. +emailExistsMessage=E-postadressen finnes allerede. + +readOnlyUserMessage=Du kan ikke oppdatere kontoen din ettersom den er skrivebeskyttet. +readOnlyPasswordMessage=Du kan ikke oppdatere passordet ditt ettersom kontoen din er skrivebeskyttet. + +successTotpMessage=Autentikator for mobiltelefon er konfigurert. +successTotpRemovedMessage=Autentikator for mobiltelefon er fjernet. + +successGrantRevokedMessage=Vellykket oppheving av rettighet. + +accountUpdatedMessage=Kontoen din har blitt oppdatert. +accountPasswordUpdatedMessage=Ditt passord har blitt oppdatert. + +missingIdentityProviderMessage=Identitetsleverand\u00F8r er ikke spesifisert. +invalidFederatedIdentityActionMessage=Ugyldig eller manglende handling. +identityProviderNotFoundMessage=Spesifisert identitetsleverand\u00F8r ikke funnet. +federatedIdentityLinkNotActiveMessage=Denne identiteten er ikke lenger aktiv. +federatedIdentityRemovingLastProviderMessage=Du kan ikke fjerne siste federerte identitet ettersom du ikke har et passord. +identityProviderRedirectErrorMessage=Redirect til identitetsleverand\u00F8r feilet. +identityProviderRemovedMessage=Fjerning av identitetsleverand\u00F8r var vellykket. +identityProviderAlreadyLinkedMessage=Federert identitet returnert av {0} er allerede koblet til en annen bruker. +staleCodeAccountMessage=Siden har utl\u00F8pt. Vennligst pr\u00F8v en gang til. +consentDenied=Samtykke avsl\u00E5tt. + +accountDisabledMessage=Konto er deaktivert, kontakt administrator. + +accountTemporarilyDisabledMessage=Konto er midlertidig deaktivert, kontakt administrator eller pr\u00F8v igjen senere. +invalidPasswordMinLengthMessage=Ugyldig passord: minimum lengde {0}. +invalidPasswordMinLowerCaseCharsMessage=Ugyldig passord: m\u00E5 inneholde minimum {0} sm\u00E5 bokstaver. +invalidPasswordMinDigitsMessage=Ugyldig passord: m\u00E5 inneholde minimum {0} sifre. +invalidPasswordMinUpperCaseCharsMessage=Ugyldig passord: m\u00E5 inneholde minimum {0} store bokstaver. +invalidPasswordMinSpecialCharsMessage=Ugyldig passord: m\u00E5 inneholde minimum {0} spesialtegn. +invalidPasswordNotUsernameMessage=Ugyldig passord: kan ikke v\u00E6re likt brukernavn. +invalidPasswordRegexPatternMessage=Ugyldig passord: tilfredsstiller ikke kravene for passord-m\u00F8nster. +invalidPasswordHistoryMessage=Ugyldig passord: kan ikke v\u00E6re likt noen av de {0} foreg\u00E5ende passordene. + +locale_ca=Catal\u00E0 +locale_de=Deutsch +locale_en=English +locale_es=Espa\u00F1ol +locale_fr=Fran\u00e7ais +locale_it=Italian +locale_ja=\u65E5\u672C\u8A9E +locale_no=Norsk +locale_pt-BR=Portugu\u00EAs (Brasil) +locale_ru=\u0420\u0443\u0441\u0441\u043A\u0438\u0439 diff --git a/themes/src/main/resources/theme/base/account/theme.properties b/themes/src/main/resources/theme/base/account/theme.properties index 2f853e89da..2cf724db33 100644 --- a/themes/src/main/resources/theme/base/account/theme.properties +++ b/themes/src/main/resources/theme/base/account/theme.properties @@ -1 +1 @@ -locales=ca,de,en,es,fr,it,ja,pt-BR,ru \ No newline at end of file +locales=ca,de,en,es,fr,it,ja,lt,no,pt-BR,ru \ No newline at end of file diff --git a/themes/src/main/resources/theme/base/admin/messages/admin-messages_ca.properties b/themes/src/main/resources/theme/base/admin/messages/admin-messages_ca.properties index 02a307a04c..0025c882a2 100755 --- a/themes/src/main/resources/theme/base/admin/messages/admin-messages_ca.properties +++ b/themes/src/main/resources/theme/base/admin/messages/admin-messages_ca.properties @@ -218,8 +218,8 @@ assertion-consumer-post-binding-url=Assertion Consumer Service POST Binding URL assertion-consumer-post-binding-url.tooltip=SAML POST Binding URL for the client''s assertion consumer service (login responses). You can leave this blank if you do not have a URL for this binding. assertion-consumer-redirect-binding-url=Assertion Consumer Service Redirect Binding URL assertion-consumer-redirect-binding-url.tooltip=Assertion Consumer Service Redirect Binding URL -logout-service-binding-post-url=URL d''enlla\u00E7 SAML POST per a la desconnexi\u00F3 -logout-service-binding-post-url.tooltip=URL d''enlla\u00E7 SAML POST per a la desconnexi\u00F3 \u00FAnica del client. Pots deixar-ho en blanc si est\u00E0s fent servir un enlla\u00E7 diferent. +logout-service-post-binding-url=URL d''enlla\u00E7 SAML POST per a la desconnexi\u00F3 +logout-service-post-binding-url.tooltip=URL d''enlla\u00E7 SAML POST per a la desconnexi\u00F3 \u00FAnica del client. Pots deixar-ho en blanc si est\u00E0s fent servir un enlla\u00E7 diferent. logout-service-redir-binding-url=URL d''enlla\u00E7 SAML de redirecci\u00F3 per a la desconnexi\u00F3 logout-service-redir-binding-url.tooltip=URL d''enlla\u00E7 SAML de redirecci\u00F3 per a la desconnexi\u00F3 \u00FAnica del client. Pots deixar-ho en blanc si est\u00E0s fent servir un enlla\u00E7 diferent. diff --git a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties index 4f15d6d51e..d3cdd9ee82 100644 --- a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties +++ b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties @@ -164,6 +164,12 @@ usermodel.clientRoleMapping.rolePrefix.label=Client Role prefix usermodel.clientRoleMapping.rolePrefix.tooltip=A prefix for each client role (optional). usermodel.realmRoleMapping.rolePrefix.label=Realm Role prefix usermodel.realmRoleMapping.rolePrefix.tooltip=A prefix for each Realm Role (optional). +sectorIdentifierUri.label=Sector Identifier URI +sectorIdentifierUri.tooltip=Providers that use pairwise sub values and support Dynamic Client Registration SHOULD use the sector_identifier_uri parameter. It provides a way for a group of websites under common administrative control to have consistent pairwise sub values independent of the individual domain names. It also provides a way for Clients to change redirect_uri domains without having to reregister all of their users. +pairwiseSubAlgorithmSalt.label=Salt +pairwiseSubAlgorithmSalt.tooltip=Salt used when calculating the pairwise subject identifier. If left blank, a salt will be generated. + + # client details clients.tooltip=Clients are trusted browser apps and web services in a realm. These clients can request a login. You can also define client specific roles. @@ -240,14 +246,16 @@ fine-oidc-endpoint-conf=Fine Grain OpenID Connect Configuration fine-oidc-endpoint-conf.tooltip=Expand this section to configure advanced settings of this client related to OpenID Connect protocol user-info-signed-response-alg=User Info Signed Response Algorithm user-info-signed-response-alg.tooltip=JWA algorithm used for signed User Info Endpoint response. If set to 'unsigned', then User Info Response won't be signed and will be returned in application/json format. +request-object-signature-alg=Request Object Signature Algorithm +request-object-signature-alg.tooltip=JWA algorithm, which client needs to use when sending OIDC request object specified by 'request' or 'request_uri' parameters. If set to 'any', then Request object can be signed by any algorithm (including 'none' ). fine-saml-endpoint-conf=Fine Grain SAML Endpoint Configuration fine-saml-endpoint-conf.tooltip=Expand this section to configure exact URLs for Assertion Consumer and Single Logout Service. assertion-consumer-post-binding-url=Assertion Consumer Service POST Binding URL assertion-consumer-post-binding-url.tooltip=SAML POST Binding URL for the client's assertion consumer service (login responses). You can leave this blank if you do not have a URL for this binding. assertion-consumer-redirect-binding-url=Assertion Consumer Service Redirect Binding URL assertion-consumer-redirect-binding-url.tooltip=SAML Redirect Binding URL for the client's assertion consumer service (login responses). You can leave this blank if you do not have a URL for this binding. -logout-service-binding-post-url=Logout Service POST Binding URL -logout-service-binding-post-url.tooltip=SAML POST Binding URL for the client's single logout service. You can leave this blank if you are using a different binding +logout-service-post-binding-url=Logout Service POST Binding URL +logout-service-post-binding-url.tooltip=SAML POST Binding URL for the client's single logout service. You can leave this blank if you are using a different binding logout-service-redir-binding-url=Logout Service Redirect Binding URL logout-service-redir-binding-url.tooltip=SAML Redirect Binding URL for the client's single logout service. You can leave this blank if you are using a different binding. @@ -316,7 +324,7 @@ available-roles=Available Roles add-selected=Add selected associated-roles=Associated Roles composite.associated-realm-roles.tooltip=Realm level roles associated with this composite role. -composite.available-realm-roles.tooltip=Realm level roles associated with this composite role. +composite.available-realm-roles.tooltip=Realm level roles that you can associate to this composite role. remove-selected=Remove selected client-roles=Client Roles select-client-to-view-roles=Select client to view roles for client @@ -564,7 +572,7 @@ realm-default-roles.tooltip=Realm level roles assigned to new users. default.available-roles-client.tooltip=Roles from this client that are assignable as a default. client-default-roles=Client Default Roles client-default-roles.tooltip=Roles from this client assigned as a default role. -composite.available-roles.tooltip=Realm level roles associated with this composite role. +composite.available-roles.tooltip=Realm level roles that you can associate to this composite role. composite.associated-roles.tooltip=Realm level roles associated with this composite role. composite.available-roles-client.tooltip=Roles from this client that you can associate to this composite role. composite.associated-roles-client.tooltip=Client roles associated with this composite role. @@ -806,6 +814,7 @@ top-level-flow-type.tooltip=What kind of top level flow is it? Type 'client' is create-execution-flow=Create Execution Flow flow-type=Flow Type flow.form.type=form +flow.generic.type=generic flow-type.tooltip=What kind of form is it form-provider=Form Provider default-groups.tooltip=Newly created or registered users will automatically be added to these groups @@ -842,6 +851,7 @@ include-representation=Include Representation include-representation.tooltip=Include JSON representation for create and update requests. clear-admin-events.tooltip=Deletes all admin events in the database. server-version=Server Version +server-profile=Server Profile info=Info providers=Providers server-time=Server Time @@ -983,13 +993,13 @@ authz-required=Required authz-import-config.tooltip=Import a JSON file containing authorization settings for this resource server. authz-policy-enforcement-mode=Policy Enforcement Mode -authz-policy-enforcement-mode.tooltip=The policy enforcement mode dictates how policies are enforced when evaluating authorization requests. 'Enforcing' means requests are denied by default even when there is no policy associated with a given resource. 'Permissive' means requests are allowed even when there is no policy associated with a given resource. 'Disabled' completely disables the evaluation of policies and allow access to any resource. +authz-policy-enforcement-mode.tooltip=The policy enforcement mode dictates how policies are enforced when evaluating authorization requests. 'Enforcing' means requests are denied by default even when there is no policy associated with a given resource. 'Permissive' means requests are allowed even when there is no policy associated with a given resource. 'Disabled' completely disables the evaluation of policies and allows access to any resource. authz-policy-enforcement-mode-enforcing=Enforcing authz-policy-enforcement-mode-permissive=Permissive authz-policy-enforcement-mode-disabled=Disabled authz-remote-resource-management=Remote Resource Management -authz-remote-resource-management.tooltip=Should resources be managed remotely by the resource server? If false, resources can only be managed from this admin console. +authz-remote-resource-management.tooltip=Should resources be managed remotely by the resource server? If false, resources can be managed only from this admin console. authz-export-settings=Export Settings authz-export-settings.tooltip=Export and download all authorization settings for this resource server. @@ -1032,7 +1042,7 @@ authz-policy-logic.tooltip=The logic dictates how the policy decision should be authz-policy-apply-policy=Apply Policy authz-policy-apply-policy.tooltip=Specifies all the policies that must be applied to the scopes defined by this policy or permission. authz-policy-decision-strategy=Decision Strategy -authz-policy-decision-strategy.tooltip=The decision strategy dictates how the policies associated with a given permission are evaluated and how a final decision is obtained. 'Affirmative' means that at least one policy must evaluate to a positive decision in order to the overall decision be also positive. 'Unanimous' means that all policies must evaluate to a positive decision in order to the overall decision be also positive. 'Consensus' means that the number of positive decisions must be greater than the number of negative decisions. If the number of positive and negative is the same, the final decision will be negative. +authz-policy-decision-strategy.tooltip=The decision strategy dictates how the policies associated with a given permission are evaluated and how a final decision is obtained. 'Affirmative' means that at least one policy must evaluate to a positive decision in order for the final decision to be also positive. 'Unanimous' means that all policies must evaluate to a positive decision in order for the final decision to be also positive. 'Consensus' means that the number of positive decisions must be greater than the number of negative decisions. If the number of positive and negative is the same, the final decision will be negative. authz-policy-decision-strategy-affirmative=Affirmative authz-policy-decision-strategy-unanimous=Unanimous authz-policy-decision-strategy-consensus=Consensus @@ -1056,15 +1066,15 @@ authz-policy-time-not-before.tooltip=Defines the time before which the policy MU authz-policy-time-not-on-after=Not On or After authz-policy-time-not-on-after.tooltip=Defines the time after which the policy MUST NOT be granted. Only granted if current date/time is before or equal to this value. authz-policy-time-day-month=Day of Month -authz-policy-time-day-month.tooltip=Defines the day of month before/equal which policy MUST be granted. You can also provide a range period by filling the second field with the day of month before/equal which the policy MUST be granted. In this case, the policy would be granted if current day of month is between/equal the two values you provided. +authz-policy-time-day-month.tooltip=Defines the day of month which the policy MUST be granted. You can also provide a range by filling the second field. In this case, permission is granted only if current day of month is between or equal to the two values you provided. authz-policy-time-month=Month -authz-policy-time-month.tooltip=Defines the month before/equal which policy MUST be granted. You can also provide a range period by filling the second field with the month before/equal which the policy MUST be granted. In this case, the policy would be granted if current month is between/equal the two values you provided. +authz-policy-time-month.tooltip=Defines the month which the policy MUST be granted. You can also provide a range by filling the second field. In this case, permission is granted only if current month is between or equal to the two values you provided. authz-policy-time-year=Year -authz-policy-time-year.tooltip=Defines the year before/equal which policy MUST be granted. You can also provide a range period by filling the second field with the year before/equal which the policy MUST be granted. In this case, the policy would be granted if current year is between/equal the two values you provided. +authz-policy-time-year.tooltip=Defines the year which the policy MUST be granted. You can also provide a range by filling the second field. In this case, permission is granted only if current year is between or equal to the two values you provided. authz-policy-time-hour=Hour -authz-policy-time-hour.tooltip=Defines the hour before/equal which policy MUST be granted. You can also provide a range period by filling the second field with the hour before/equal which the policy MUST be granted. In this case, the policy would be granted if current hour is between/equal the two values you provided. +authz-policy-time-hour.tooltip=Defines the hour which the policy MUST be granted. You can also provide a range by filling the second field. In this case, permission is granted only if current hour is between or equal to the two values you provided. authz-policy-time-minute=Minute -authz-policy-time-minute.tooltip=Defines the minute before/equal which policy MUST be granted. You can also provide a range period by filling the second field with the minute before/equal which the policy MUST be granted. In this case, the policy would be granted if current minute is between/equal the two values you provided. +authz-policy-time-minute.tooltip=Defines the minute which the policy MUST be granted. You can also provide a range by filling the second field. In this case, permission is granted only if current minute is between or equal to the two values you provided. # Authz Drools Policy Detail authz-add-drools-policy=Add Drools Policy diff --git a/themes/src/main/resources/theme/base/admin/messages/admin-messages_es.properties b/themes/src/main/resources/theme/base/admin/messages/admin-messages_es.properties index 2a1807cc9a..fb17ff6a24 100755 --- a/themes/src/main/resources/theme/base/admin/messages/admin-messages_es.properties +++ b/themes/src/main/resources/theme/base/admin/messages/admin-messages_es.properties @@ -218,8 +218,8 @@ assertion-consumer-post-binding-url=Assertion Consumer Service POST Binding URL assertion-consumer-post-binding-url.tooltip=SAML POST Binding URL for the client''s assertion consumer service (login responses). You can leave this blank if you do not have a URL for this binding. assertion-consumer-redirect-binding-url=Assertion Consumer Service Redirect Binding URL assertion-consumer-redirect-binding-url.tooltip=Assertion Consumer Service Redirect Binding URL -logout-service-binding-post-url=URL de enlace SAML POST para la desconexi\u00F3n -logout-service-binding-post-url.tooltip=URL de enlace SAML POST para la desconexi\u00F3n \u00FAnica del cliente. Puedes dejar esto en blanco si est\u00E1s usando un enlace distinto. +logout-service-post-binding-url=URL de enlace SAML POST para la desconexi\u00F3n +logout-service-post-binding-url.tooltip=URL de enlace SAML POST para la desconexi\u00F3n \u00FAnica del cliente. Puedes dejar esto en blanco si est\u00E1s usando un enlace distinto. logout-service-redir-binding-url=URL de enlace SAML de redirecci\u00F3n para la desconexi\u00F3n logout-service-redir-binding-url.tooltip=URL de enlace SAML de redirecci\u00F3n para la desconexi\u00F3n \u00FAnica del cliente. Puedes dejar esto en blanco si est\u00E1s usando un enlace distinto. diff --git a/themes/src/main/resources/theme/base/admin/messages/admin-messages_fr.properties b/themes/src/main/resources/theme/base/admin/messages/admin-messages_fr.properties index 93e7b14d5b..b923234bca 100644 --- a/themes/src/main/resources/theme/base/admin/messages/admin-messages_fr.properties +++ b/themes/src/main/resources/theme/base/admin/messages/admin-messages_fr.properties @@ -27,7 +27,7 @@ sslRequired.option.all=toutes les requ\u00eates sslRequired.option.external=les requ\u00eates externes sslRequired.option.none=aucun sslRequired.tooltip=Si le HTTPS est requis ? ''aucun'' signifie que le HTTPS n''est requis pour aucune adresse IP cliente. ''les requ\u00eates externes'' signifie que localhost et les adresses IP priv\u00e9es peuvent acc\u00e9der sans HTTPS. ''toutes les requ\u00eates'' signifie que le protocole HTTPS est obligatoire pour toutes les adresses IP. -publicKey=Clef publique key +publicKey=Clef publique gen-new-keys=Cr\u00e9ation de nouvelle clef certificate=Certificat host=H\u00f4te diff --git a/themes/src/main/resources/theme/base/admin/messages/admin-messages_lt.properties b/themes/src/main/resources/theme/base/admin/messages/admin-messages_lt.properties new file mode 100644 index 0000000000..b65bb7cc1b --- /dev/null +++ b/themes/src/main/resources/theme/base/admin/messages/admin-messages_lt.properties @@ -0,0 +1,1131 @@ +consoleTitle=Keycloak administravimo konsol\u0117 + +# Common messages +enabled=\u012Egalintas +name=Pavadinimas +displayName=Rodomas pavadinimas +displayNameHtml=Rodomas pavadinimas HTML formatu +save=Saugoti +cancel=At\u0161aukti +onText=ON +offText=OFF +client=Klientas +clients=Klientai +clear=I\u0161valyti +selectOne=Pasirinkite vien\u0105... + +true=Taip +false=Ne + +endpoints=Prieigos adresai + +# Realm settings +realm-detail.enabled.tooltip=Naudotojai ir programos prie srities gali prieiti tik tuomet, kai ji \u012Fgalinta +realm-detail.oidc-endpoints.tooltip=Atidaromas langas su OpenID Connect prieigos URL adresais +registrationAllowed=Naudotoj\u0173 registracija +registrationAllowed.tooltip=\u012Egalina naudotoj\u0173 registravimosi s\u0105saj\u0105. Prisijungimo lange rodoma nuoroda \u012F registravimosi puslap\u012F. +registrationEmailAsUsername=El. pa\u0161tas kaip naudojo vardas +registrationEmailAsUsername.tooltip=Jei \u012Fgalintas tuomet naudotojo vardo laukas registravimosi lange yra slepiamas ir naujai besiregistruojantiems naudotojams el. pa\u0161to adresas naudojamas kaip naudotojo vardas. +editUsernameAllowed=Naudotojo vardo redagavimas +editUsernameAllowed.tooltip=Jei \u012Fgalintas, tuomet naudotojas gali keisti savo naudotojo vard\u0105. +resetPasswordAllowed=Slapta\u017Eod\u017Eio priminimas +resetPasswordAllowed.tooltip=Prisijungimo lange rodoma nuoroda pamir\u0161to slapta\u017Eod\u017Eio atk\u016Brimui. +rememberMe=Prisiminti mane +rememberMe.tooltip=Prisijungimo lange rodyti pasirinkim\u0105 leid\u017Eiant\u012F naudotojui likti prisijungus netgi tuomet, kai nar\u0161ykl\u0117 yra i\u0161jungiama/\u012Fjungiama tol, kol nepasibaigia prisijungimo sesija. +verifyEmail=El. pa\u0161to patvirtinimas +verifyEmail.tooltip=Reikalauti naudotojo patvirtinti el. pa\u0161to adres\u0105 pirmojo prisijungimo metu. +sslRequired=Reikalauti SSL +sslRequired.option.all=visoms u\u017Eklausoms +sslRequired.option.external=i\u0161orin\u0117ms u\u017Eklausoms +sslRequired.option.none=niekada +sslRequired.tooltip=Ar HTTPS privalomas? 'niekada' - HTTPS nereikalaujamas. 'i\u0161orin\u0117ms u\u017Eklausoms' - jungiantis i\u0161 localhost ar serverio IP adres\u0173 galima prieiti ir per HTTP. 'visoms u\u017Eklausoms' - HTTPS reikalaujamas jungiantis i\u0161 vis\u0173 IP adres\u0173. +publicKey=Vie\u0161as raktas +privateKey=Privatus raktas +gen-new-keys=Generuoti naujus raktus +certificate=Sertifikatas +host=Serveris +smtp-host=SMTP serveris +port=Prievadas +smtp-port=SMTP prievadas (numatyta reik\u0161m\u0117 25) +from=Nuo +sender-email-addr=Siunt\u0117jo el. pa\u0161to adresas +enable-ssl=\u012Egalinti SSL +enable-start-tls=\u012Egalinti StartTLS +enable-auth=\u012Egalinti autentifikacij\u0105 +username=Naudotojo vardas +login-username=Prisijungimui naudojamas naudotojo vardas +password=Slapta\u017Eodis +login-password=Prisijungimui naudojamas slapta\u017Eodis +login-theme=Prisijungimo lango tema +login-theme.tooltip=Pasirinkite kaip atrodys J\u016Bs\u0173 prisijungimo, TOTP, teisi\u0173 suteikimo, naudotoj\u0173 registracijos ir slapta\u017Eod\u017Ei\u0173 priminimo langai. +account-theme=Naudotojo profilio tema +account-theme.tooltip=Pasirinkite kaip atrodys naudotojo profilio valdymo langai. +admin-console-theme=Administravimo konsol\u0117s tema +select-theme-admin-console=Pasirinkite kaip atrodys administravimo konsol\u0117s langai. +email-theme=El. pa\u0161to tema +select-theme-email=Pasirinkite kaip atrodys siun\u010Diami el. pa\u0161to lai\u0161kai. +i18n-enabled=Daugiakalbyst\u0117s palaikymas +supported-locales=Palaikomos kalbos +supported-locales.placeholder=Pasirinkite arba \u012Fra\u0161ykite kalbos pavadinim\u0105 +default-locale=Numatyta kalba +realm-cache-clear=Srities pod\u0117lis +realm-cache-clear.tooltip=I\u0161 vis\u0173 sri\u010Di\u0173 pa\u0161alinama visa pod\u0117lyje (cache) esanti informacija +user-cache-clear=Naudotoj\u0173 pod\u0117lis +user-cache-clear.tooltip=I\u0161 vis\u0173 sri\u010Di\u0173 pa\u0161alinama visa naudotoj\u0173 pod\u0117lyje (cache) esanti informacija +revoke-refresh-token=Prieigos rakt\u0105 naudoti tik kart\u0105 +revoke-refresh-token.tooltip=Jei \u012Fgalintas, tuomet atnaujinimo raktai (Refresh Token) gali b\u016Bti naudojami tik vien\u0105 kart\u0105. Kitu atveju - atnaujinimo raktai gali b\u016Bti pernaudojami daugel\u012F kart\u0173. +sso-session-idle=SSO sesijos neveikimo laikas +seconds=Sekund\u0117s +minutes=Minut\u0117s +hours=Valandos +days=Dienos +sso-session-max=SSO sesijos maksimalus laikas +sso-session-idle.tooltip=Laikas, po kurio neaktyvi sesija bus u\u017Ebaigta. Sesijos pasibaigimo metu visi raktai (Tokens) ir nar\u0161ykli\u0173 sesijos sunaikinamos. +sso-session-max.tooltip=Laikas, po kurio prisijungimo sesija yra sunaikinama. Sesijos pasibaigimo metu visi raktai (Tokens) ir nar\u0161ykli\u0173 sesijos sunaikinamos. +offline-session-idle=Neprisijungusios sesijos neveikimo laikas +offline-session-idle.tooltip=Darbo neprisijungus sesijos neveikimo laikas, po kurio neaktyvi sesija bus u\u017Ebaigta. Darbo neprisijungus metu, prisijungimo raktai turi b\u016Bti atnaujinami bent kart\u0105 per nurodyt\u0105 period\u0105. Kitu atveju sesijos galiojmas bus sustabdytas. +access-token-lifespan=Prisijungimo rakto galiojimo laikas +access-token-lifespan.tooltip=Laikas, po kurio prisijungimui naudojamas raktas (Access Token) nustoja galioti. Rekomenduojama, kad \u0161ios reik\u0161m\u0117s galiojimas b\u016Bt\u0173 reliatyviai trumpas palyginus su SSO galiojimo laiku. +access-token-lifespan-for-implicit-flow=Prisijungimo rakto galiojimo laikas (Implicit Flow) +access-token-lifespan-for-implicit-flow.tooltip=Laikas, po kurio prisijungimui naudojamas OpenID Connect Implicit Flow raktas nustoja galioti. Rekomenduojama, kad \u0161ios reik\u0161m\u0117s galiojimas b\u016Bt\u0173 reliatyviai trumpas palyginus su SSO galiojimo laiku. \u0160is parametras skiriasi nuo 'Prisijungimo rakto galiojimo laikas', nes n\u0117ra galimyb\u0117s atnaujinti prieigos rakto naudojant OpenID Connect Implicit Flow. +client-login-timeout=Kliento prisijungimui skirtas laikas +client-login-timeout.tooltip=Laikas, per kur\u012F klientas turi u\u017Ebaigti prisijungimo proces\u0105. Normaliu atveju reik\u0161m\u0117 tur\u0117t\u0173 b\u016Bti 1 minut\u0117. +login-timeout=Naudotojo prisijungimui skirtas laikas +login-timeout.tooltip=Laikas, per kur\u012F naudotojas turi u\u017Ebaigti prisijungimo proces\u0105. Rekomenduojamas pakankamai ilgas laiko tarpas. Pvz. 30 minu\u010Di\u0173 ar daugiau. +login-action-timeout=Naudotojo prisijungimo veiksmui skirtas laikas +login-action-timeout.tooltip=Laikas, per kur\u012F naudotojas turi u\u017Ebaigti su prisijungimu susijus\u012F veiksm\u0105. Pavyzd\u017Eiui atnaujinti slapta\u017Eod\u012F ar sukonfig\u016Bruoti TOTP. Rekomenduojamas laikas - 5 minut\u0117s ar daugiau. +headers=Antra\u0161t\u0117s +brute-force-detection=Grubios j\u0117gos ataka +x-frame-options=X-Frame-Options +x-frame-options-tooltip=Numatyta reik\u0161m\u0117 draud\u017Eia puslapius naudoti kitose svetain\u0117se per iframe (paspauskite antra\u0161t\u0119 nor\u0117dami gauti daugiau informacijos) +content-sec-policy=Content-Security-Policy +content-sec-policy-tooltip=Numatyta reik\u0161m\u0117 draud\u017Eia puslapius naudoti kitose svetain\u0117se per iframe (paspauskite antra\u0161t\u0119 nor\u0117dami gauti daugiau informacijos) +content-type-options=X-Content-Type-Options +content-type-options-tooltip=Numatyta reik\u0161m\u0117 draud\u017Eia Internet Explorer ir Google Chrome atlikti priimti kitokias MIME reik\u0161mes (MIME-sniffing) nei deklaruotas turinio tipas (content-type) (paspauskite antra\u0161t\u0119 nor\u0117dami gauti daugiau informacijos) +max-login-failures=Maksimalus bandym\u0173 prisijungim\u0173 skai\u010Dius +max-login-failures.tooltip=Pasiekus maksimal\u0173 nes\u0117kming\u0173 bandym\u0173 prisijungti skai\u010Di\u0173 \u012Fjungiamas specialus r\u0117\u017Eimas, kuomet laukimo intervalas yra didinamas po kiekvieno sekan\u010Dio neteisingo bandymo. +wait-increment=Laukimo laiko didinimas po +wait-increment.tooltip=Laikas, kur\u012F naudotojo prisijungimai yra draud\u017Eiami, kai n\u0117s\u0117kming\u0173 bandym\u0173 skai\u010Dius pasiekia nustatyt\u0105 rib\u0105 +quick-login-check-millis=Per greito bandymo prisijungti laikas milisekund\u0117mis +quick-login-check-millis.tooltip=Jei n\u0117s\u0117kmingi bandymai prisijungti seka vienas kit\u0105 per greitai, tuomet naudotojo paskyra yra u\u017Erakinama. +min-quick-login-wait=Per greito bandymo prisijungti u\u017Erakinimo laikas +min-quick-login-wait.tooltip=Laikas, kur\u012F naudotojo prisijungimai yra draud\u017Eiami, kai n\u0117s\u0117kmingi bandymai prisijungti seka vienas kit\u0105 per greitai. +max-wait=Maksimalus u\u017Erakinimo laikas +max-wait.tooltip=Maksimalus laikas, kuomet naudotojo paskyra yra u\u017Erakinama po nes\u0117kming\u0173 bandym\u0173 prisijungti. +failure-reset-time=Pamir\u0161ti nepavykusius prisijungimus po +failure-reset-time.tooltip=Laikas, po kurio nepavyk\u0119 prisijungimai bus pamir\u0161ti +realm-tab-login=Prisijungimas +realm-tab-keys=Raktai +realm-tab-email=El. pa\u0161tas +realm-tab-themes=Temos +realm-tab-cache=Pod\u0117lis +realm-tab-tokens=Raktai +realm-tab-client-initial-access=Pradiniai prieigos raktai +realm-tab-security-defenses=Saugos priemon\u0117s +realm-tab-general=Bendra informacija +add-realm=Prid\u0117ti srit\u012F + +#Session settings +realm-sessions=Srities sesijos +revocation=At\u0161aukimai +logout-all=Atjungti visus +active-sessions=Aktyvios sesijos +sessions=Sesijos +not-before=Ne anks\u010Diau +not-before.tooltip=At\u0161aukti visus raktus i\u0161duotus prie\u0161 nurodyt\u0105 dat\u0105. +set-to-now=Parinkti dabartin\u0119 dat\u0105 +push=Informuoti apie at\u0161aukim\u0105 +push.tooltip=Visus klientus, kurie turi administravimo URL, informuoti apie nauj\u0105 rakt\u0173 at\u0161aukimo taisykl\u0119. + +#Protocol Mapper +usermodel.prop.label=Atributas +usermodel.prop.tooltip=S\u0105sajos UserModel atributo metodo pavadinimas. Pavyzd\u017Eiui reik\u0161m\u0117 'email' atitinka UserMode.getEmail() metod\u0105. +usermodel.attr.label=Naudotojo atributas +usermodel.attr.tooltip=I\u0161saugoto naudotojo atributo pavadinimas kuris naudojamas UserModel.attribute rinkinyje. +userSession.modelNote.label=Naudotojo sesijos pastaba +userSession.modelNote.tooltip=I\u0161saugotos naudotojo sesijos pastaba, kuri saugoma UserSessionModel.note rinkinyje. +multivalued.label=Daugiareik\u0161mis +multivalued.tooltip=Nurodo, kad atributas gali tur\u0117ti daugiau nei vien\u0105 reik\u0161m\u0119. Jei pa\u017Eym\u0117tas, tuomet visos reik\u0161m\u0117s nustatomos kaip privalomos. Kitu atveju privaloma tik pirmoji reik\u0161m\u0117. +selectRole.label=Parinkite rol\u0119 +selectRole.tooltip=Kair\u0117je pus\u0117je esan\u010Diame laukelyje \u012Fveskite rol\u0117s pavadinim\u0105 arba paspauskite Rinktis nor\u0117dami nurodyti pageidaujam\u0105 rol\u0119. +tokenClaimName.label=Reikalaujamo rakto pavadinimas +tokenClaimName.tooltip=\u012E rakt\u0105 \u012Fterpiamas privalomas atributas. Galite nurodyte piln\u0105 keli\u0105 iki atributo, pavyzd\u017Eiui 'address.street'. Pateiktu atveju bus sukuriamas sud\u0117tinis (nested) JSON objektas. +jsonType.label=Privalomo atributo JSON tipas +jsonType.tooltip=Naudojamas JSON lauko tipas, kuris turi b\u016Bti u\u017Epildomas rakto privalomoje JSON informacijoje. Galimi tipai: long, int, boolean ir String. +includeInIdToken.label=Prid\u0117ti prie ID rakto +includeInIdToken.tooltip=Ar privaloma informacija turi b\u016Bti pridedama prie ID rakto? +includeInAccessToken.label=Prid\u0117ti prie prieigos rakto +includeInAccessToken.tooltip=Ar privaloma informacija turi b\u016Bti pridedama prie prieigos rakto? +includeInUserInfo.label=Prid\u0117ti prie naudotojo informacijos +includeInUserInfo.tooltip=Ar privaloma informacija turi b\u016Bti pridedama prie naudotojo informacijos? +usermodel.clientRoleMapping.clientId.label=Kliento ID +usermodel.clientRoleMapping.clientId.tooltip=Kliento ID naudojamas roli\u0173 atribut\u0173 susiejime +usermodel.clientRoleMapping.rolePrefix.label=Kliento rol\u0117s prefiksas +usermodel.clientRoleMapping.rolePrefix.tooltip=Prefiksas, pridedamas prie\u0161 kiekvien\u0105 kliento rol\u0119 (neprivalomas) +usermodel.realmRoleMapping.rolePrefix.label=Srities rol\u0117s prefiksas +usermodel.realmRoleMapping.rolePrefix.tooltip=Prefiksas, pridedamas prie\u0161 kiekvien\u0105 srities rol\u0119 (neprivalomas) + +# client details +clients.tooltip=Klientai - tai srities nar\u0161ykl\u0117s program\u0117l\u0117s arba tinklin\u0117s paslaugos, kuriomis pasitikima. Klientai gali jungtis prie sistemos. Klientams galima nurodyti specifines roles. +search.placeholder=Ie\u0161koti... +create=Sukurti +import=Importuoti +client-id=Kliento ID +base-url=Pagrindinis URL +actions=Veiksmai +not-defined=Nenurodyta +edit=Redaguoti +delete=Trinti +no-results=Rezultat\u0173 n\u0117ra +no-clients-available=N\u0117ra sukonfig\u016Bruot\u0173 klient\u0173 +add-client=Prid\u0117ti klient\u0105 +select-file=Parinkti rinkmen\u0105 +view-details=Per\u017Ei\u016Br\u0117ti detaliau +clear-import=I\u0161valyti importuojamas rinkmenas +client-id.tooltip=Identifikatorius, naudojamas URI adresuose ir prieigos raktuose. Pavyzd\u017Eiui 'my-client'. SAML protokolo atveju, \u0161i\u0105 reik\u0161m\u0119 tikimasi gauti kaip authn u\u017Eklausos siunt\u0117j\u0105 +client.name.tooltip=Reik\u0161m\u0117, kuri rodoma naudotojams. Pavyzd\u017Eiui 'My Client'. Galimos lokalizuotos reik\u0161m\u0117s - pavyzd\u017Eiui\: ${my_client} +client.enabled.tooltip=Klientai, kurie n\u0117ra \u012Fgalinti, negali inicijuoti prisijungimo arba gauti prieigos raktus. +consent-required=Reikalingas patvirtinimas +consent-required.tooltip=Jei \u012Fgalinta, tuomet naudotojai privalo patvirtinti, kad pageidauja prisijungti prie kliento (programos). +client-protocol=Kliento protokolas +client-protocol.tooltip='OpenID connect' leid\u017Eia klientams tikrinti galutinio naudotojo tapatyb\u0119 remiantis autorizacijos serverio atlikta autentifikacija. 'SAML' \u012Fgalina \u017Einiatinklio, \u012Fskaitant skirting\u0173 domen\u0173 atvejus, vieningos autentifikacijos ir autorizacijos scenarijus perduodant informacij\u0105 saugiose \u017Einut\u0117se. +access-type=Prieigos tipas +access-type.tooltip='Konfidencialus' klientai nor\u0117dami inicijuoti prisijungimo protokol\u0105 privalo perduoti slapt\u0105 kod\u0105. 'Vie\u0161as' klientai neprivalo perduoti slapto kodo. 'Tik ne\u0161\u0117jas' klientai - tai tinklin\u0117s paslaugos, kurios niekada neinicijuoja prisijungimo. +standard-flow-enabled=\u012Egalinta standartin\u0117 seka +standard-flow-enabled.tooltip=\u012Egalina standartin\u012F OpenID Connect nukreipim\u0105, kuomet autentifikacijos metu yra perduodamas autorizacijos kodas. OpenID Connect arba OAuth2 specifikacijos terminais tai rei\u0161kia 'Authorization Code Flow' \u012Fgalinim\u0105 \u0161iam klientui. +implicit-flow-enabled=\u012Egalinta i\u0161reik\u0161tin\u0117 seka +implicit-flow-enabled.tooltip=\u012Egalina OpenID Connect nukreipim\u0105, kuomet autentifikacijos metu n\u0117ra perduodamas autorizacijos kodas. OpenID Connect arba OAuth2 specifikacijos terminais tai rei\u0161kia 'Implicit Flow' \u012Fgalinim\u0105 \u0161iam klientui. +direct-access-grants-enabled=\u012Egalintas tiesiogin\u0117s prieigos suteikimas +direct-access-grants-enabled.tooltip=\u012Egalina tiesiogin\u012F prieigos suteikim\u0105, kuomet klientas turi prieig\u0105 prie naudotojo vardo ir slapta\u017Eod\u017Eio ir prieigos rakt\u0173 gavimui \u0161iais duomenimis gali tiesiogiai apsikeisti su Keycloak serveriu. OAuth2 specifikacijos terminais, \u0161iam klientui \u012Fgalinimas 'Resource Owner Password Credentials Grant'. +service-accounts-enabled=\u012Egalintas paslaugos naudotojas +service-accounts-enabled.tooltip=\u012Egalina klient\u0105 autentifikuotis su Keycloak serveriu ir gauti dedikuot\u0105 prieigos rakt\u0105 skirt\u0105 \u0161iam klientui. OAuth2 specifikacijos terminais, tai rei\u0161kia 'Client Credentials Grant' teis\u0119 \u0161iam klientui. +include-authnstatement=\u012Etraukti AuthnStatement +include-authnstatement.tooltip=Ar prisijungimo b\u016Bdas ir laikas \u0161ur\u0117t\u0173 b\u016Bti \u012Ftraukiami \u012F prisijungimo operacijos atsakym\u0105? +sign-documents=Pasira\u0161yti dokumentus +sign-documents.tooltip=Ar SAML dokumentai turi b\u016Bt\u012F pasira\u0161omi \u0161ios srities? +sign-assertions=Pasira\u0161yti sprendinius +sign-assertions.tooltip=Ar SAML sprendiniai SAML dokumentuose turi b\u016Bti pasira\u0161omi? \u0160is nustatymas neb\u016Btinas, kuomet naudojamas viso dokumento pasira\u0161ymas. +signature-algorithm=Para\u0161o algoritmas +signature-algorithm.tooltip=Para\u0161o algoritmas naudojamas dokument\u0173 pasira\u0161ymui. +canonicalization-method=Standartizavimo metodas +canonicalization-method.tooltip=XML para\u0161o metodas. +encrypt-assertions=U\u017Ekoduoti sprendinius +encrypt-assertions.tooltip=Ar SAML sprendiniai turi b\u016Bti u\u017Ekoduojami kliento vie\u0161uoju raktu naudojant AES? +client-signature-required=Privalomas kliento para\u0161as +client-signature-required.tooltip=Ar kliento siun\u010Diamos SAML u\u017Eklausos ir atsakymai bus pasira\u0161yti? Jei taip, tuomet ar juos privaloma tikrinti? +force-post-binding=Priverstinai naudoti POST s\u0105ry\u0161\u012F +force-post-binding.tooltip=Visuomet naudoti POST s\u0105ry\u0161\u012F siun\u010Diant atsakymus. +front-channel-logout=I\u0161registravimas per nar\u0161ykl\u0119 +front-channel-logout.tooltip=Jei \u012Fgalinta, tuomet atsijungimas atliekamas nar\u0161ykl\u0117s nukreipimu \u012F kliento puslap\u012F. Kitu atveju, atsijungimas atliekamas perduodant serveris-serveris u\u017Eklaus\u0105. +force-name-id-format=Priverstinai naudoti NameID format\u0105 +force-name-id-format.tooltip=Ignoruoti NameID tapatyb\u0117s identifikatoriaus format\u0105, naudojant administratoriaus konsol\u0117je nurodyt\u0105 format\u0105. +name-id-format=NameID formatas +name-id-format.tooltip=Koks tapatyb\u0117s identifikatoriaus formatas turi b\u016Bti naudojamas. +root-url=\u0160akninis URL +root-url.tooltip=Prie reliatyvi\u0173 nuorod\u0173 pridedamas \u0161akninis URL +valid-redirect-uris=Leid\u017Eiamos nukreipimo nuorodos +valid-redirect-uris.tooltip=Nukreipimo URI \u0161ablonas, kuomet nar\u0161yklei leid\u017Eiama nukreipti naudotoj\u0105 po s\u0117kmingos autentifikacijos ar atsijungimo metu. Leid\u017Eiami pakaitos simboliai, pvz. 'http://pavyzdys.lt/*'. Leid\u017Eiami reliatyv\u016Bs keliai pvz. /mano/reliatyvus/kelias/*. Reliatyvumas skai\u010Diuojamas nuo kliento \u0161akninio URL (jei nurodyta) arba nuo autentifikacijos serverio \u0161akninio adreso. SAML atveju, kuomet tikimasi gav\u0117jo paslaugos URL \u012Ftraukimo \u012F prisijungimo u\u017Eklaus\u0105, privaloma nurodyti teisingus URI \u0161ablonus. +base-url.tooltip=Numatytas URL, kuris turi b\u016Bti naudojamas naudotojo nukreipimui atgal \u012F klient\u0105. +admin-url=Administravimo URL +admin-url.tooltip=Kliento administravimo tinklin\u0117s s\u0105sajos URL. \u012Era\u0161yti tuomet, kai klientas palaiko adapterio REST API. \u0160is REST API leid\u017Eia autentifikacijos serveriui perduoti at\u0161aukimo ir kitas su administravimu susijusias taisykles. Da\u017Eniausiai \u0161is URL sutampa su kliento pagrindiniu URL. +master-saml-processing-url=\u0160akninis SAML apdorojimo URL +master-saml-processing-url.tooltip=Kuomet sukonfig\u016Bruotas, \u0161is URL bus naudojamas visoms, 'SP's Assertion Consumer' ir 'Single Logout Services' u\u017Eklausoms. Detalioje SAML prieigos adres\u0173 konfig\u016Bravimo skyriuje \u0161ios reik\u0161m\u0117s gali b\u016Bti atskirai pakeistos. +idp-sso-url-ref=IDP inicijuojamo SSO URL fragmento pavadinimas +idp-sso-url-ref.tooltip=Pavadinimas, kuris IDP inicijuoto SSO prisijungimo metu, perduodamas klientui per URL fragment\u0105. Palikus tu\u0161\u010Di\u0105 reik\u0161m\u0119 IDP inicjuojam\u0105 SSO prisijungimo funkcionalumas i\u0161jungiamas. \u0160is fragmentas buv naudojamas formuojant \u0161i\u0105 nuorod\u0105: {server-root}/realms/{realm}/protocol/saml/clients/{client-url-name} +idp-sso-relay-state=IDP inicijuotos SSO b\u016Bsenos perdavimas +idp-sso-relay-state.tooltip=SSO b\u016Bsenos parametro (RelayState) perdavimas kartu su IDP inicijuota SSO SAML u\u017Eklausa. +web-origins=\u0160aknin\u0117s nuorodos +web-origins.tooltip=Leid\u017Eiamos CORS nuorodos. Nor\u0117dami leisti nukreipim\u0105 \u012F teisingas nuorodas, naudokite '+'. Nor\u0117dami leisti visas nuorodas, naudokite '*'. +fine-oidc-endpoint-conf=Detalioji OpenID prisijungimo konfig\u016Bracija +fine-oidc-endpoint-conf.tooltip=Nor\u0117dami konfig\u016Bruoti kliento s\u0105sajos su OpenID prisijungimo protokolu i\u0161pl\u0117stines nuostatas, i\u0161skleiskite \u0161\u012F skyri\u0173. +user-info-signed-response-alg=Naudotojo informacijos pasira\u0161yto atsako algoritmas +user-info-signed-response-alg.tooltip=JWA algoritmas naudojamas pasira\u0161yti naudotojo informacijos prieigos ta\u0161ko atsak\u0105. Jei nustatyta 'unsigned', tuomet naudotojo informacijos atsakas nebus pasira\u0161ytas ir bus gr\u0105\u017Eintas application/json formatu. +request-object-signature-alg=U\u017Eklausos objekto para\u0161o algoritmas +request-object-signature-alg.tooltip=JWA algoritmas, kur\u012F klientas naudoja siun\u010Diant OIDC u\u017Eklausos objekt\u0105, nusakyt\u0105 'request' arba 'request_uri' parameterais. Jei nustatyta 'any', tuomet u\u017Eklausos objektas gali b\u016Bti nepasira\u0161ytas arba pasira\u0161ytas bet kuriuo algoritmu. +fine-saml-endpoint-conf=Detalioji SAML prieigos ta\u0161k\u0173 konfig\u016Bracija +fine-saml-endpoint-conf.tooltip=Nor\u0117dami konfig\u016Bruoti sprendini\u0173 pri\u0117mimo ir vieningo atsijungimo paslaugas, i\u0161skleiskite \u0161\u012F skyri\u0173. +assertion-consumer-post-binding-url=Sprendini\u0173 naudotojo paslaugos POST jungties URL +assertion-consumer-post-binding-url.tooltip=Kliento sprendini\u0173 pri\u0117mimo paslaugos (prisijungimo rezultat\u0173) SAML POST jungties URL. Jei toki\u0173 jung\u010Di\u0173 neturite, tuomet palikite tu\u0161\u010Dias reik\u0161mes. +assertion-consumer-redirect-binding-url=Sprendini\u0173 pri\u0117mimo paslaugos nukreipimo jungties URL +assertion-consumer-redirect-binding-url.tooltip=Kliento sprendinio pri\u0117mimo paslaugos SAML nukreipimo jungties URL (prisijungimo atsakymams). Jei toki\u0173 jung\u010Di\u0173 neturite, tuomet palikite tu\u0161\u010Dias reik\u0161mes. +logout-service-binding-post-url=Atsijungimo paslaugos POST jungties URL +logout-service-binding-post-url.tooltip=Kliento vieningo atsijungimo SAML POST jungties URL. Jei naudojate kitas jungtis, tuomet \u0161ias reik\u0161mes galite palikti neu\u017Epildytas. +logout-service-redir-binding-url=Atsijungimo paslaugos nukreipimo jungties URL +logout-service-redir-binding-url.tooltip=Kliento vieningo atsijungimo paslaugos SAML nukreipimo jungties. Jei naudojate kitas jungtis, tuomet \u0161ias reik\u0161mes galite palikti neu\u017Epildytas. + +# client import +import-client=\u012Ediegti programos nustatymus +format-option=Formato pasirinkimas +select-format=Pasirinkite format\u0105 +import-file=Importuoti rinkmen\u0105 + +# client tabs +settings=Nustatymai +credentials=Prisijungimo duomenys +saml-keys=SAML raktai +roles=Rol\u0117s +mappers=Atribut\u0173 atitikmenys +mappers.tooltip=Protokolo atribut\u0173 susiejimas atlieka rakt\u0173 ir dokument\u0173 transformacijas. Naudotojo duomenys gali b\u016Bti ver\u010Diami \u012F protokolo teiginius, arba tiesiog transformuoti bet kurias u\u017Eklausas perduodamas tarp kliento ir autentifikacijos serverio. +scope=Apimtis +scope.tooltip=Apimties atitikmen\u0173 parinkimas leid\u017Eia apriboti, kurios naudotojo rol\u0117s kartu su raktu bus perduodamos klientui. +sessions.tooltip=Per\u017Ei\u016Br\u0117ti \u0161io kliento aktyvias sesijas. Matysite \u0161iuo metu prisijungusius naudotojus bei j\u0173 prisijungimo laikus. +offline-access=Darbas neprisijungus +offline-access.tooltip=Per\u017Ei\u016Br\u0117ti \u0161io kliento darbo neprisijungus r\u0117\u017Eimo aktyvias sesijas. Matysite naudotojus, kuriems yra i\u0161duoti darbo neprisijungus raktai bei j\u0173 i\u0161davimo laikus. Nor\u0117dami at\u0161aukti visus \u0161iam klientui i\u0161duotus raktus, eikite \u012F at\u0161aukim\u0173 kortel\u0119 ir pasirinkite 'Parinkti dabartin\u0119 dat\u0105' +clustering=Klasteriai +installation=Diegimas +installation.tooltip=Klient\u0173 konfig\u016Bravimo pagalbin\u0117 priemon\u0117, padedanti sugeneruoti klient\u0173 adapteri\u0173 konfig\u016Bracijas, kurias galima atsisi\u0173sti, kopijuoti ar \u012Fkelti i\u0161 i\u0161karpin\u0117s +service-account-roles=Paslaugos paskyros rol\u0117s +service-account-roles.tooltip=Dedikuot\u0173 roli\u0173 priskyrimas \u0161ios paslaugos naudotojui + +# client credentials +client-authenticator=Kliento autentifikavimo priemon\u0117s +client-authenticator.tooltip=Kliento autentifikavimo priemon\u0117s naudojamos kliento autentifikavimuisi \u012F Keycloak server\u012F +certificate.tooltip=Kliento sertifikatas naudojamas kliento i\u0161duot\u0173 ir priva\u010Diu raktu pasira\u0161yt\u0173 JWT prieigos rakt\u0173 tikrinimui. +publicKey.tooltip=Kliento i\u0161duotas vie\u0161asis raktas pasira\u0161ytas kliento priva\u010Diu raktu ir skirtas JWT tikrinimui. +no-client-certificate-configured=Nesukonfig\u016Bruotas nei vienas kliento sertifikatas +gen-new-keys-and-cert=Nauj\u0173 rakt\u0173 ir sertifikat\u0173 generavimas +import-certificate=Importuoti sertifikat\u0105 +gen-client-private-key=Generuoti kliento privat\u0173 rakt\u0105 +generate-private-key=Generuoti privat\u0173 rakt\u0105 +archive-format=Archyvo formatas +archive-format.tooltip=Java rakt\u0173 saugykla (keystore) arba PKCS12 formato rinkmena. +key-alias=Rakto pseudonimas +key-alias.tooltip=Privataus rakto ir sertifikato rinkmenos pseudonimas. +key-password=Rakto slapta\u017Eodis +key-password.tooltip=Slapta\u017Eod\u017Ei\u0173 saugykloje esan\u010Dio privataus rakto slapta\u017Eodis +store-password=Saugyklos slapta\u017Eodis +store-password.tooltip=Slapta\u017Eodis, reikalingas norint atidaryti slapta\u017Eod\u017Ei\u0173 saugykl\u0105 +generate-and-download=Generuoti ir atsisi\u0173sti +client-certificate-import=Kliento sertifikato importavimas +import-client-certificate=Importuoti kliento sertifikatus +jwt-import.key-alias.tooltip=Slapta\u017Eod\u017Ei\u0173 saugyklos pseudonimas +secret=Slaptas kodas +regenerate-secret=Pergeneruoti slapt\u0105 kod\u0105 +registrationAccessToken=Registracijos prieigos raktas +registrationAccessToken.regenerate=Pergeneruoti registracijos prieigos rakt\u0105 +registrationAccessToken.tooltip=Registracijos prieigos raktas klientams suteikia prieig\u0105 prie klient\u0173 registracijos paslaugos +add-role=Prid\u0117ti rol\u0119 +role-name=Rol\u0117s pavadinimas +composite=Sud\u0117tinis +description=Apra\u0161ymas +no-client-roles-available=Kliento rol\u0117s nesukonfig\u016Bruotos +scope-param-required=Privalomas taikymo srities parametras +scope-param-required.tooltip=\u0160i rol\u0117 suteikiama tik tuo atveju, kai taikymo srities parametras su rol\u0117s vardu panaudotas autorizacijos u\u017Eklausoje ar rakte. +composite-roles=Sud\u0117tin\u0117s rol\u0117s +composite-roles.tooltip=Visos susietos rol\u0117s bus automati\u0161kai priskiriamos naudotojui prisikiriant \u0161i\u0105 sud\u0117tin\u0119 rol\u0119. +realm-roles=Srities rol\u0117s +available-roles=Galimos rol\u0117s +add-selected=Prid\u0117ti pa\u017Eym\u0117tas +associated-roles=Priskirtos rol\u0117s +composite.associated-realm-roles.tooltip=Srities apmities rol\u0117s susietos su \u0161ia sud\u0117tine role. +composite.available-realm-roles.tooltip=Srities apmities rol\u0117s, kurias galima susieti su \u0161ia sud\u0117tine role. +remove-selected=Pa\u0161alinti pa\u017Eym\u0117tas +client-roles=Kliento rol\u0117s +select-client-to-view-roles=Nor\u0117dami pamatyti priskirtas roles pa\u017Eym\u0117kite klient\u0105 +available-roles.tooltip=\u0160io kliento rol\u0117s, kurios gali b\u016Bti priskiritos \u0161iai kompozicinei rolei. +client.associated-roles.tooltip=Su \u0161iuo klientu susietos sud\u0117tin\u0117s rol\u0117s. +add-builtin=Prid\u0117ti numatytuosius +category=Kategorija +type=Tipas +no-mappers-available=N\u0117ra susiet\u0173 atribut\u0173 +add-builtin-protocol-mappers=Prid\u0117ti numatytuosius protokolo atribut\u0173 susiejimus +add-builtin-protocol-mapper=Prid\u0117ti numatytuosius protokolo atribut\u0173 susiejimus +scope-mappings=atribut\u0173 susiejimo taikymo sritis +full-scope-allowed=Taikymas pilna apimtimi +full-scope-allowed.tooltip=\u012Egalinimo atveju visi apribojimai i\u0161jungiami +scope.available-roles.tooltip=Srities lygio rol\u0117s, kurios gali b\u016Bti priskiriamos \u0161iai taikymo sri\u010Diai. +assigned-roles=Priskirtos rol\u0117s +assigned-roles.tooltip=Srities lygio rol\u0117s, kurios yra priskirtos \u0161iai taikymo sri\u010Diai. +effective-roles=Aktyvios rol\u0117s +realm.effective-roles.tooltip=Priskirtos srities lygio rol\u0117s, kurios gali gali b\u016Bti paveld\u0117tos i\u0161 sud\u0117tini\u0173 roli\u0173. +select-client-roles.tooltip=Nor\u0117dami pamatyti visas roles pa\u017Eym\u0117kite klient\u0105 +assign.available-roles.tooltip=Kliento rol\u0117s, kurias galima priskirti. +client.assigned-roles.tooltip=Priskirtos kliento rol\u0117s. +client.effective-roles.tooltip=Priskirtos kliento rol\u0117s, kurios gali gali b\u016Bti paveld\u0117tos i\u0161 sud\u0117tini\u0173 roli\u0173. +basic-configuration=Pagrindin\u0117 konfig\u016Bracija +node-reregistration-timeout=Mazgo persiregistravimui skirtas laikas +node-reregistration-timeout.tooltip=Nurodykite maksimal\u0173 laiko interval\u0105, per kur\u012F mazgai privalo i\u0161 naujo prisiregistruoti. Jei mazgas neatsi\u0173s persiregistravimo u\u017Eklausos per nurodyt\u0105 laik\u0105, tuomet \u0161is mazgas bus i\u0161registruojamas i\u0161 Keycloak +registered-cluster-nodes=Registruoti klasterio mazgus +register-node-manually=Registruoti mazg\u0105 rankiniu b\u016Bdu +test-cluster-availability=Tikrinti ar mazgas prieinamas +last-registration=V\u0117liausia registracija +node-host=Mazgo serveris +no-registered-cluster-nodes=Nepriregistuotas nei vienas klasterio mazgas +cluster-nodes=Klasterio mazgai +add-node=Prid\u0117ti mazg\u0105 +active-sessions.tooltip=\u0160io kliento aktyvi\u0173 naudotoj\u0173 sesij\u0173 skai\u010Dius. +show-sessions=Rodyti sesijas +show-sessions.tooltip=D\u0117mesio, \u0161is veiksmas gali ilgai u\u017Etrukti priklausomai nuo aktyvi\u0173 sesij\u0173 skai\u010Diaus. +user=Naudotojas +from-ip=Prisijungimo IP +session-start=Sesijos prad\u017Eios laikas +first-page=Pirmas puslapis +previous-page=Atgalinis puslapis +next-page=Sekantis puslapis +client-revoke.not-before.tooltip=At\u0161aukti visus \u0161io kliento raktus i\u0161duotus prie\u0161 nurodyt\u0105 dat\u0105. +client-revoke.push.tooltip=Kuomet nurodytas administravimo URL, taisykl\u0117 perduodama klientui. +select-a-format=Formato parinkimas +download=Atsisi\u0173sti +offline-tokens=Darbo neprisijungus raktai +offline-tokens.tooltip=Rakt\u0173 skai\u010Dius, kurie naudojami darbui neprisijungus +show-offline-tokens=Rodyti raktus +show-offline-tokens.tooltip=D\u0117mesio, \u0161is veiksmas gali ilgai u\u017Etrukti priklausomai nuo aktyvi\u0173 darbo neprisijungus sesij\u0173 skai\u010Diaus. +token-issued=Rakto i\u0161davimo laikas +last-access=V\u0117liausios prieigos laikas +last-refresh=V\u0117liausio atnaujinimo laikas +key-export=Eksportuoti rakt\u0105 +key-import=Importuoti rakt\u0105 +export-saml-key=Eksportuoti SAML rakt\u0105 +import-saml-key=Importuoti SAML rakt\u0105 +realm-certificate-alias=Srities sertifikato pseudonimas +realm-certificate-alias.tooltip=Srities sertifikato, kuris taip pat saugomas saugykloje, pseudonimas. +signing-key=Pasira\u0161ymo raktas +saml-signing-key=SAML pasira\u0161ymo raktas. +private-key=Privatus raktas +generate-new-keys=Generuoti naujus raktus +export=Eksportuoti +encryption-key=U\u017Ekodavimo raktas +saml-encryption-key.tooltip=SAML u\u017Ekodavimo raktas. +service-accounts=Paslaugos naudotojo profiliai +service-account.available-roles.tooltip=\u0160ios paslaugos paskyrai galimos priskirti srities rol\u0117s +service-account.assigned-roles.tooltip=Paslaugos paskyrai priskirtos srities rol\u0117s. +service-account-is-not-enabled-for=Kliento {{client}} paslaugos paskyra ne\u012Fgalinta +create-protocol-mappers=Protokolo atitkmen\u0173 susiejimas +create-protocol-mapper=Protokolo atitkmenens susiejimas +protocol=Protokolas +protocol.tooltip=Protokolas... +id=ID +mapper.name.tooltip=Atitikmens susiejimo vardas. +mapper.consent-required.tooltip=Ar teikiant laikin\u0105 prieig\u0105 naudotojas privalo pateikti sutikim\u0105 d\u0117l duomen\u0173 perdavimo? +consent-text=Sutikimo tekstas +consent-text.tooltip=Tekstas, kuris rodomas naudotojo sutikimo puslapyje. +mapper-type=Atitikmens tipas +mapper-type.tooltip=Atitikmens tipas +# realm identity providers +identity-providers=Tapatyb\u0117s teik\u0117jai +table-of-identity-providers=Tapatyb\u0117s teik\u0117j\u0173 s\u0105ra\u0161as +add-provider.placeholder=Prid\u0117ti teik\u0117j\u0105... +provider=Teik\u0117jas +gui-order=GUI eili\u0161kumas +first-broker-login-flow=Pirmojo prisijungimo eiga +post-broker-login-flow=Sekan\u010Di\u0173 prisijungim\u0173 eiga +redirect-uri=Nukreipimo URI +redirect-uri.tooltip=Tapatyb\u0117s teik\u0117jo konfig\u016Bravimo nuoroda. +alias=Pseudonimas +identity-provider.alias.tooltip=Pseudonimas, kuris vienareik\u0161mi\u0161kai identifikuoja tapatyb\u0117s teik\u0117j\u0105 ir yra naudojamas konstruojant nukreipimo nuorod\u0105. +identity-provider.enabled.tooltip=\u012Egalinti \u0161\u012F tapatyb\u0117s teik\u0117j\u0105. +authenticate-by-default=Autentifikuoti i\u0161 karto +identity-provider.authenticate-by-default.tooltip=Jei \u012Fgalinta, tuomet bus bandoma autentifikuoti naudotoj\u0105 prie\u0161 parodant prisijungimo lang\u0105. +store-tokens=Saugoti raktus +identity-provider.store-tokens.tooltip=Jei \u012Fgalinta, tuomet po naudotoj\u0173 prisijungimo, prieigos raktai bus i\u0161saugoti. +stored-tokens-readable=Saugoti raktus skaitomame formate +identity-provider.stored-tokens-readable.tooltip=Jei \u012Fgalinta, tuomet naudotojai gali per\u017Ei\u016Br\u0117ti i\u0161saugotus prieigos raktus. \u012Egalinama broker.read-token rol\u0117. +update-profile-on-first-login=Profilio duomen\u0173 atnaujinimas pirmojo prisijungimo metu +on=On +off=Off +on-missing-info=Kuomet tr\u016Bksta informacijos +update-profile-on-first-login.tooltip=Nurodykite s\u0105lygas, kuomet naudotojas privalo atnaujinti savo profil\u012F pirmojo prisijungimo metu. +trust-email=El. pa\u0161tas patikimas +trust-email.tooltip=Jei \u012Fgalintas, tuomet \u0161io tapatyb\u0117s teik\u0117jo pateiktas el. pa\u0161to adresas laikomas patikimu ir, nepaisant bendr\u0173j\u0173 srities nustatym\u0173, n\u0117ra papildomai tikrinamas. +gui-order.tooltip=Eili\u0161kum\u0105 GUI lange (pvz. Prisijungimo langas) nurodantis skai\u010Dius +first-broker-login-flow.tooltip=Autentifikacijos eigos pseudonimas, kuris bus su\u017Eadintas \u0161io tapatyb\u0117s teik\u0117jo naudotojui prisijungus pirm\u0105 kart\u0105. Terminas 'pirmas kartas' rei\u0161kia, kad Keycloak sistemoje nebuvo saugomas naudotojo profilis susietas su autentifikuotu \u0161io tapatyb\u0117s teik\u0117jo naudotoju. +post-broker-login-flow.tooltip=Autentifikacijos eigos pseudonimas, kuris bus su\u017Eadintas po kiekvieno prisijungimo naudojant \u0161\u012F tapatyb\u0117s teik\u0117j\u0105. Naudingas tuomet, kai atlikti papildomus tikrinimus (pvz. OTP). Palikite tu\u0161\u010Di\u0105 reik\u0161m\u0119 jei nenorite su\u017Eadinti papildom\u0173 tikrinim\u0173 autentifikatoriumi jungiantis per \u0161\u012F tapatyb\u0117s teik\u0117j\u0105. Tur\u0117kite omenyje, kad autentifikatoriaus realizacijos turi daryti prielaid\u0105, kad ClientSession naudotojas yra tapatyb\u0117s teik\u0117jo nustatytas. +openid-connect-config=OpenID prisijungimo konfig\u016Bracija +openid-connect-config.tooltip=OIDC SP ir i\u0161orinio IDP konfig\u016Bracija. +authorization-url=Autorizacijos URL +authorization-url.tooltip=Autorizacijos URL adresas. +token-url=Prieigos rakt\u0173 URL +token-url.tooltip=Prieigos rakt\u0173 URL. +logout-url=Atsijungimo URL +identity-provider.logout-url.tooltip=Adresas, kuris turi b\u016Bti naudojamas norint atjungti naudotoj\u0105 nuo i\u0161orinio tapatyb\u0117s teik\u0117jo. +backchannel-logout=Foninis atjungimas +backchannel-logout.tooltip=Ar i\u0161orinis tapatyb\u0117s teik\u0117jas palaiko serveris-serveris naudotojo atjungimo b\u016Bd\u0105? +user-info-url=Naudotojo informacijos URL +user-info-url.tooltip=Naudotojo informacijos URL. Neprivalomas. +identity-provider.client-id.tooltip=Kliento identifikatorius u\u017Eregistruotas tapatyb\u0117s teik\u0117jo sistemoje. +client-secret=Kliento slaptas kodas +show-secret=Rodysi slapt\u0105 kod\u0105 +hide-secret=Sl\u0117pti slapt\u0105 kod\u0105 +client-secret.tooltip=Kliento slaptas kodas u\u017Eregistruotas tapatyb\u0117s teik\u0117jo sistemoje. +issuer=I\u0161dav\u0117jas +issuer.tooltip=I\u0161dav\u0117jo identifikatorius perduodamas i\u0161dav\u0117jo atsakyme. Tikrinimas nebus atliekamas jei reik\u0161m\u0117 tu\u0161\u010Dia. +default-scopes=Numatytosios taikymo sritys +identity-provider.default-scopes.tooltip=Taikymos sritys, kurios siun\u010Diamos autorizavimo u\u017Eklausoje. Reik\u0161m\u0117s turi b\u016Bti atskirtos tarpo simboliu. Numatyta reik\u0161m\u0117 - 'openid'. +prompt=Raginimas +unspecified.option=nenurodyta +none.option=jokio +consent.option=sutikimo tekstas +login.option=prisijungimas +select-account.option=paskyros pasirinkimas +prompt.tooltip=Nurodo, ar autorizacijos serveris galutini\u0173 naudotoj\u0173 reikalauja pakartotinai patvirtinti sutikim\u0105 ar prisijungti. +validate-signatures=Para\u0161o tikrinimas +identity-provider.validate-signatures.tooltip=\u012Egalinamas i\u0161orini\u0173 IDP para\u0161\u0173 tikrinimas. +validating-public-key=Vie\u0161as raktas para\u0161o tikrinimui +identity-provider.validating-public-key.tooltip=PEM formato vie\u0161asis raktas, kuris turi b\u016Bti naudojamas i\u0161orinio IDP para\u0161t\u0173 tikrinimui. +import-external-idp-config=Importuoti i\u0161orinio IDP konfig\u016Bracij\u0105 +import-external-idp-config.tooltip=Leid\u017Eia \u012Fkelti konfig\u016Bracin\u0119 rinkmen\u0105 arba nurodyti atsisiuntimo URL su i\u0161orinio IDP metaduomenimis. +import-from-url=Importuoti i\u0161 URL +identity-provider.import-from-url.tooltip=Importuoti metaduomenis i\u0161 nutolusio IDP aptikimo apra\u0161o (IDP discovery descriptor). +import-from-file=Importuoti i\u0161 rinkmenos +identity-provider.import-from-file.tooltip=Importuoti metaduomenis i\u0161 rinkmenos, kuri\u0105 atsisiunt\u0117te i\u0161 IDP aptikimo apra\u0161o (IDP discovery descriptor). +saml-config=SAML konfig\u016Bracija +identity-provider.saml-config.tooltip=SAML SP ir i\u0161oriniu IDP konfig\u016Bracija. +single-signon-service-url=Vieningo prisijungimo paslaugos URL +saml.single-signon-service-url.tooltip=Adresas, kuriuo turi b\u016Bti siun\u010Diamos autentifikacijos u\u017Eklausos (SAML AuthnRequest). +single-logout-service-url=Vieningo atsijungimo paslaugos URL +saml.single-logout-service-url.tooltip=Adresas, kuriuo turi b\u016Bti siun\u010Diamos naudotojo atjungimo u\u017Eklausos. +nameid-policy-format=NameID taisykli\u0173 formatas +nameid-policy-format.tooltip=Nurodykite URI nuorod\u0105 atitinkan\u010Di\u0105 vardo identifikatoriaus format\u0105. Numatyta reik\u0161m\u0117 urn:oasis:names:tc:SAML:2.0:nameid-format:persistent. +http-post-binding-response=Si\u0173sti atsakymus HTTP-POST +http-post-binding-response.tooltip=Jei \u012Fgalinta, tuomet atsakymai siun\u010Diami HTTP-POST saistymu. Kitu atveju bus naudojamas HTTP-REDIRECT. +http-post-binding-for-authn-request=Si\u0173sti AuthnRequest HTTP-POST +http-post-binding-for-authn-request.tooltip=Jei \u012Fgalinta, tuomet AuthnRequest siun\u010Diami HTTP-POST saistymu. Kitu atveju bus naudojamas HTTP-REDIRECT. +want-authn-requests-signed=Reikalaujami pasira\u0161yt\u0173 AuthnRequests +want-authn-requests-signed.tooltip=Nurodykite, ar tapatyb\u0117s teik\u0117jas tikisi pasira\u0161yt\u0173 AuthnRequest u\u017Eklaus\u0173. +force-authentication=Priverstin\u0117 autentifikacija +identity-provider.force-authentication.tooltip=Jei \u012Fgalinta, tuomet tapatyb\u0117s teik\u0117jas privalo autentifikuoti naudotoj\u0105 i\u0161 naujo nepasitikint ankstesniu prisijungimu. +validate-signature=Para\u0161o tikrinimas +saml.validate-signature.tooltip=\u012Ejungti/i\u0161jungti SAML atsakym\u0173 para\u0161o tikrinim\u0105. +validating-x509-certificate=X509 sertifikatas tikrinimui +validating-x509-certificate.tooltip=PEM formato sertifikatas, kuris turi b\u016Bti naudojamas para\u0161\u0173 tikrinimui. +saml.import-from-url.tooltip=Importuoti metaduomenis i\u0161 nutolusio IDP SAML subjekto apra\u0161o. +social.client-id.tooltip=Kliento identifikatorius u\u017Eregistruotas tapatyb\u0117s teik\u0117jo sistemoje. +social.client-secret.tooltip=Kliento saugos kodas u\u017Eregistruotas tapatyb\u0117s teik\u0117jo sistemoje. +social.default-scopes.tooltip=Autorizacijos metu siun\u010Diamos taikymo sritys. Galim\u0173 reik\u0161mi\u0173 s\u0105ra\u0161o, skirtuko ir numatytos reik\u0161m\u0117s ie\u0161kokite tapatyb\u0117s teik\u0117jo sistemos dokumentacijoje.. +key=Raktas +stackoverflow.key.tooltip=Stack Overflow kliento registracijos metu gautas raktas. + +# User federation +sync-ldap-roles-to-keycloak=Sinchronizuoti LDAP roles \u012F Keycloak +sync-keycloak-roles-to-ldap=Sinchronizuoti Keycloak roles \u012F LDAP +sync-ldap-groups-to-keycloak=Sinchronizuoti LDAP grupes \u012F Keycloak +sync-keycloak-groups-to-ldap=Sinchronizuoti Keycloak grupes \u012F LDAP + +realms=Sritys +realm=Sritis + +identity-provider-mappers=Tapatyb\u0117s teik\u0117jo atitikmen\u0173 susiejimai +create-identity-provider-mapper=Sukurti tapatyb\u0117s teik\u0117jo atitikmens susiejim\u0105 +add-identity-provider-mapper=Prid\u0117ti tapatyb\u0117s teik\u0117jo atitikmens susiejim\u0105 +client.description.tooltip=Nurodomas kliento apra\u0161as. Pavyzd\u017Eiui 'Mano laiko lenteli\u0173 klientas'. Palaikomos lokalizuotos reik\u0161m\u0117s. Pavyzd\u017Eiui\: ${my_client_description} + +expires=Galioja iki +expiration=Galiojimas +expiration.tooltip=Nurodykite kiek laiko galios prieigos raktas +count=Kiekis +count.tooltip=Nurodykite kiek klient\u0173 gali b\u016Bti sukurti naudojant prieigos rakt\u0105 +remainingCount=Lik\u0119s kiekis +created=Sukurta +back=Atgal +initial-access-tokens=Pradiniai prieigos raktai +add-initial-access-tokens=Prid\u0117ti pradin\u012F prieigos rakt\u0105 +initial-access-token=Pradinis prieigos raktas +initial-access.copyPaste.tooltip=Nukopijuokite ir \u012Fklijuokite prieigos rakt\u0105 prie\u0161 i\u0161eidami i\u0161 \u0161io puslapio. V\u0117liau negal\u0117site kopijuoti \u0161i\u0173 prieigos rakt\u0173. +continue=T\u0119sti +initial-access-token.confirm.title=Kopijuoti pradinius prieigos raktus +initial-access-token.confirm.text=Pra\u0161ome \u012Fsitikinti, kad nusikopijavote pradinius prieigos raktus nes v\u0117liau prie rakt\u0173 nebegal\u0117site prieiti +no-initial-access-available=N\u0117ra galim\u0173 pradini\u0173 prieigos rak\u0161\u0173 + +trusted-hosts-legend=Patikimi kliento registracijos serveriai +trusted-hosts-legend.tooltip=Serveri\u0173 vardai, kuriais pasitikima kliento registracijos metu. Klient\u0173 registravimo u\u017Eklausos i\u0161 \u0161i\u0173 serveri\u0173 gali b\u016Bti siun\u010Diamos be pradini\u0173 prieigos rakt\u0173. Klient\u0173 registracijos skai\u010Dius ribojamas pagal nurodyt\u0105 kiekvieno serverio limit\u0105. +no-client-trusted-hosts-available=N\u0117ra galim\u0173 patikim\u0173 serveri\u0173 +add-client-reg-trusted-host=Prid\u0117ti patikim\u0105 server\u012F +hostname=Serverio vardas +client-reg-hostname.tooltip=Pilnas serverio vardas arba IP adresas. Klient\u0173 registracijomis su \u0161iuo serverio vardu arba IP adresu bus pasitikima ir leid\u017Eiama nauj\u0173 klient\u0173 registracija. +client-reg-count.tooltip=Limitas, kiek registravimo u\u017Eklaus\u0173 galima atsi\u0173sti i\u0161 kiekvieno serverio. Limitas bus atkurtas tik po atk\u016Brimo. +client-reg-remainingCount.tooltip=I\u0161 \u0161io serverio lik\u0119s galim\u0173 registracijos u\u017Eklaus\u0173 skai\u010Dius. Limitas bus atkurtas tik po atk\u016Brimo. +reset-remaining-count=Atk\u016Brimo limit\u0105 + +client-templates=Klient\u0173 \u0161ablonai +client-templates.tooltip=Klient\u0173 \u0161ablonai leid\u017Eia nurodyti bendr\u0105 vis\u0173 klient\u0173 konfig\u016Bracij\u0105 + +groups=Grup\u0117s + +group.add-selected.tooltip=Grupei galimos priskirti srities rol\u0117s. +group.assigned-roles.tooltip=Su \u0161ia grupe susietos srities roles +group.effective-roles.tooltip=Visos srities susietos rol\u0117s. \u0160iame s\u0105ra\u0161e taip pat rodomos visos rol\u0117s, kurios priskirtos sud\u0117tin\u0117ms rol\u0117ms. +group.available-roles.tooltip=\u0160io kliento galimos susieti rol\u0117s. +group.assigned-roles-client.tooltip=Susietos \u0161io kliento rol\u0117s. +group.effective-roles-client.tooltip=Visos \u0161io kliento susietos rol\u0117s. \u0160iame s\u0105ra\u0161e taip pat rodomos visos rol\u0117s, kurios priskirtos sud\u0117tin\u0117ms rol\u0117ms. + +default-roles=Numatytosios rol\u0117s +no-realm-roles-available=Sritis neturi roli\u0173 + +users=Naudotojai +user.add-selected.tooltip=Naudotojui galimos priskirti srities rol\u0117s. +user.assigned-roles.tooltip=Su \u0161iuo naudotoju susietos srities rol\u0117s +user.effective-roles.tooltip=Visos srities susietos rol\u0117s. \u0160iame s\u0105ra\u0161e taip pat rodomos visos rol\u0117s, kurios priskirtos sud\u0117tin\u0117ms rol\u0117ms. +user.available-roles.tooltip=\u0160io kliento galimos susieti rol\u0117s. +user.assigned-roles-client.tooltip=Susietos \u0161io kliento rol\u0117s. +user.effective-roles-client.tooltip=Visos \u0161io kliento susietos rol\u0117s. \u0160iame s\u0105ra\u0161e taip pat rodomos visos rol\u0117s, kurios priskirtos sud\u0117tin\u0117ms rol\u0117ms. +default.available-roles.tooltip=Galimos priskirti srities rol\u0117s. +realm-default-roles=Numatytosios srities rol\u0117s +realm-default-roles.tooltip=Srities rol\u0117s, kurios automati\u0161kai priskiriamos naujiems naudotojams. +default.available-roles-client.tooltip=\u0160io kliento rol\u0117s, kurios automati\u0161kai gali b\u016Bti priskiriamos naudotojams. +client-default-roles=Numatytosios kliento rol\u0117s +client-default-roles.tooltip=Kliento rol\u0117s, kurios automati\u0161kai priskiriamos naujiems naudotojams. +composite.available-roles.tooltip=Srities rol\u0117s, kurias galima susieti su \u0161ia sud\u0117tine role. +composite.associated-roles.tooltip=Srities rol\u0117s, kurios susietos su \u0161ia sud\u0117tine role. +composite.available-roles-client.tooltip=Kliento rol\u0117s, kurias galima susieti su \u0161ia sud\u0117tine role. +composite.associated-roles-client.tooltip=Kliento rol\u0117s, kurios susietos su \u0161ia sud\u0117tine role. +partial-import=Dalinis duomen\u0173 importavimas +partial-import.tooltip=Dalinis duomen\u0173 importavimas leid\u017Eia \u012Fkelti prie\u0161 tai eksportuot\u0105 JSON rinkmen\u0105 su naudotojais, klientais ir kitais resursais. + +file=Rinkmena +exported-json-file=Eksportuota JSON rinkmena +import-from-realm=\u012Ekelti i\u0161 srities +import-users=\u012Ekelti naudotojus +import-groups=\u012Ekelti grupes +import-clients=\u012Ekelti klientus +import-identity-providers=\u012Ekelti tapatyb\u0117s teik\u0117jus +import-realm-roles=\u012Ekelti srities roles +import-client-roles=\u012Ekelti klient\u0173 roles +if-resource-exists=Jei resursas egzistuoja +fail=Nevykdyti +skip=Praleisti +overwrite=Perra\u0161yti +if-resource-exists.tooltip=Nurodykite k\u0105 daryti kuomet bandoma \u012Fkelti jau egzistuojant\u012F resurs\u0105. + +action=Veiksmas +role-selector=Roli\u0173 parinkimas +realm-roles.tooltip=Srities rol\u0117s, kurias galima pasirinkti. + +select-a-role=Pasirinkti rol\u0119 +select-realm-role=Pasirinkti srities rol\u0119 +client-roles.tooltip=Kliento rol\u0117s, kurias galite pa\u017Eym\u0117ti. +select-client-role=Pasirinkti kliento rol\u0119 + +client-template=Kliento \u0161ablonas +client-template.tooltip=Kliento \u0161ablonas, i\u0161 kurio paveldima konfig\u016Bracija +client-saml-endpoint=Kliento SAML adresas +add-client-template=Kliento \u0161ablono k\u016Brimas + +manage=Valdyti +authentication=Autentifikavimas +user-federation=Naudotoj\u0173 federavimas +user-storage=Naudotoj\u0173 saugykla +events=\u012Evykiai +realm-settings=Srities nustatymai +configure=Konfig\u016Bruoti +select-realm=Pasirinkite srit\u012F +add=Prid\u0117ti + +client-template.name.tooltip=Kliento \u0161ablono pavadinimas. Privalo b\u016Bti unikalus \u0161ioje srityje +client-template.description.tooltip=Kliento \u0161ablono apra\u0161ymas +client-template.protocol.tooltip=Kurio SSO protokolo konfig\u016Bracija teikia \u0161is \u0161ablonas + +add-user-federation-provider=Prid\u0117ti naudotoj\u0173 federacijos teik\u0117ja +required-settings=Privalomi nustatymai +provider-id=Teik\u0117jo ID +console-display-name=Konsol\u0117je rodomas pavadinimas +console-display-name.tooltip=Administravimo konsol\u0117je rodomas teik\u0117jo pavadinimas. +priority=Prioritetas +priority.tooltip=Skai\u010Dius nurodantis naudotojo paie\u0161kos \u0161iame federacijos teik\u0117juje prioritet\u0105. Pirmiausia imama su ma\u017Eesniu skai\u010Diumi. +sync-settings=Sinchronizuoti nustatymus +periodic-full-sync=Pilnas periodinis sinchronizavimas +periodic-full-sync.tooltip=Ar turi b\u016Bti atliekamas periodinis pilnas teik\u0117jo naudotoj\u0173 sinchronizavimas \u012F Keycloak? +full-sync-period=Pilno sinchronizavimo intervalas +full-sync-period.tooltip=Laikas sekund\u0117mis, kas kur\u012F atliekamas pilnas naudotoj\u0173 sinchronizavimas \u012F Keycloak sistem\u0105 +periodic-changed-users-sync=Periodinis pakeitim\u0173 sinchronizavimas +periodic-changed-users-sync.tooltip=Ar turi b\u016Bti atliekamas naujai u\u017Eregistruot\u0173 naudotoj\u0173 ar naudotoj\u0173 su redaguotais profilio duomenimis periodinis sinchronizavimas \u012F Keycloak? +changed-users-sync-period=Periodinis sinchronizavimo intervalas +changed-users-sync-period.tooltip=Laikas sekund\u0117mis, kas kur\u012F atliekamas naujai u\u017Eregistruot\u0173 naudotoj\u0173 ar naudotoj\u0173 su redaguotais profilio duomenimis sinchronizavimas \u012F Keycloak +synchronize-changed-users=Sinchronizuoti naudotoj\u0173 pakeitimus +synchronize-all-users=Sinchronizuoti visus naudotojus +kerberos-realm=Kerberos sritis +kerberos-realm.tooltip=Kerberos srities pavadinimas. Pavyzd\u017Eiui FOO.ORG +server-principal=Pagrindinis serveris +server-principal.tooltip=Pilnas HTTP paslaugai skirtas pagrindinio serverio su domenu pavadinimas. Pavyzd\u017Eiui HTTP/host.foo.org@FOO.ORG +keytab=KeyTab +keytab.tooltip=Kelias iki Kerberos KeyTab rinkmenos talpinan\u010Dios prisijungimo prie pagrindinio serverio duomenis. Pavyzd\u017Eiui /etc/krb5.keytab +debug=Derinti +debug.tooltip=Ar \u012Fgalinti Krb5LoginModule veikimo prane\u0161im\u0173 ra\u0161ym\u0105 \u012F standarin\u0119 i\u0161vest\u012F derinimo r\u0117\u017Eimu? +allow-password-authentication=Leisti autentifikacij\u0105 naudojant slapta\u017Eod\u012F +allow-password-authentication.tooltip=Ar suteikti galimyb\u0119 naudotojui prisijungti prie Kerberos naudojant naudotojo vard\u0105 ir slapta\u017Eod\u012F? +edit-mode=Pakeitim\u0173 r\u0117\u017Eimas +edit-mode.tooltip=READ_ONLY rei\u0161kia, kad naudotojui neleid\u017Eiama keisti slapta\u017Eod\u017Eio ir autentifikacija visuomet bus atliekama Kerberos. UNSYNCED rei\u0161kia, kad naudotojui leid\u017Eiama keisti slapta\u017Eod\u012F saugom\u0105 Keycloak duomen\u0173 baz\u0117je ir kuris bus naudojamas autentifikacijos metu vietoj Kerberos slapta\u017Eod\u017Eio. +ldap.edit-mode.tooltip=READ_ONLY rei\u0161kia, kad LDAP saugykla bus naudojama vien tik skaitymo r\u0117\u017Eimu. WRITABLE rei\u0161kia, kad duomenys sinchronizuojami atgal \u012F LDAP pagal poreik\u012F. UNSYNCED rei\u0161kia, kad naudotoj\u0173 duomenys bus importuoti, ta\u010Diau niekuomet nesinchronizuojami atgal \u012F LDAP. +update-profile-first-login=Pirmojo prisijungimo metu atnaujinti duomenis +update-profile-first-login.tooltip=Pirmojo prisijungimo metu atnaujinti naudotojo profilio duomenis +sync-registrations=Sinchronizuoti registracijas +ldap.sync-registrations.tooltip=Ar naujai u\u017Esiregistrav\u0119 naudotojai tur\u0117t\u0173 b\u016Bti sinchonizuojami \u012F LDAP saugykl\u0105? Federavimo teik\u0117jas, \u012F kur\u012F sinchronizuojami nauji naudotojai, parenkamas pagal prioritet\u0105. Pirmiausia imami su ma\u017Eiausiu skai\u010Diumi. +vendor=Gamintojas +ldap.vendor.tooltip=LDAP gamintojas (teik\u0117jas) +username-ldap-attribute=Prisijungimo vardo LDAP atributas +ldap-attribute-name-for-username=LDAP atributo pavadinimas, kuriame saugomas naudotojo prisijungimo vardas +username-ldap-attribute.tooltip=LDAP atributas, kuris turi b\u016Bti susietas su Keycloak naudotojo prisijungimo vardu. Su daugeliu LDAP serveri\u0173 gali b\u016Bti naudojamas 'uid' atributas. ActiveDirectory gali b\u016Bti 'sAMAccountName' arba 'cn'. Reikalaujama, kad nurodytas LDAP atributas b\u016Bt\u0173 u\u017Epildytas visiems naudotojams kurie importuojami i\u0161 LDAP \u012F Keycloak. +rdn-ldap-attribute=RDN LDAP atributas +ldap-attribute-name-for-user-rdn=LDAP atributo pavadinimas, kuriame saugomas naudotojo RDN +rdn-ldap-attribute.tooltip=LDAP atributas, kuris naudojamas kaip RDN (Relative Distinguished Name) vietoj tipinio naudotojo DN (Distinguished Name). Da\u017Eniausiai reik\u0161m\u0117 sutampa su prisijungimo vardo LDAP atributu, ta\u010Diau pastarasis n\u0117ra privalomas. Pavyzd\u017Eiui ActiveDirectory da\u017Eniausiai kaip RDN atributas naudojamas 'cn' nors prisijungimo vardo atributas b\u016Bna 'sAMAccountName'. +uuid-ldap-attribute=UUID LDAP atributas +ldap-attribute-name-for-uuid=LDAP atributo pavadinimas, kuriame saugomas UUID +uuid-ldap-attribute.tooltip=LDAP atributas, kuris naudojamas kaip unikalus LDAP objekt\u0173 identifikatorius (UUID). Daugelis LDAP serveri\u0173 naudoja 'entryUUID', ta\u010Diau pasitaiko ir i\u0161im\u010Di\u0173. Pavyzd\u017Eiui ActiveDirectory naudojamas 'objectGUID' atributas. Jei j\u016Bs\u0173 LDAP serveris nepalaiko UUID atribut\u0173, tuomet galite naudoti bet kur\u012F kit\u0105 atribut\u0105 kuris u\u017Etikrina LDAP naudotoj\u0173 unikalum\u0105. Pavyzd\u017Eiui 'uid' arba 'entryDN'. +user-object-classes=Naudotoj\u0173 objekt\u0173 klas\u0117s +ldap-user-object-classes.placeholder=LDAP naudotoj\u0173 objekt\u0173 klas\u0117s (skiriamos kableliu) +ldap.user-object-classes.tooltip=LDAP atributo objectClass atskirtos kableliu reik\u0161m\u0117s skirtos LDAP naudotojo objektui. Pavyzd\u017Eiui: 'inetOrgPerson, organizationalPerson' . Naujai registruoti Keycloak naudotojai bus \u012Fra\u0161yti \u0161 LDAP su visomis nurodytomis objekt\u0173 klas\u0117mis. Egzistuojantys LDAP naudotoj\u0173 objektai randami tik tuomet, kai jie turi visas \u0161ias nurodytas objekto klases. + +ldap-connection-url=LDAP jungties URL +ldap-users-dn=LDAP naudotoj\u0173 DN +ldap-bind-dn=LDAP prisijungimo DN +ldap-bind-credentials=LDAP prisijungimo slapta\u017Eodis +ldap-filter=LDAP filtras + +connection-url=Jungties URL +ldap.connection-url.tooltip=Jungties \u012F LDAP server\u012F URL +test-connection=Tikrinti jungt\u012F +users-dn=Naudotoj\u0173 DN +ldap.users-dn.tooltip=\u0160aknin\u0117 LDAP med\u017Eio DN (Distinguished Name) kuriame saugomi naudotojai. Jei pavyzd\u017Eiui tipinio naudotojo DN yra 'uid=john,ou=users,dc=example,dc=com' tuomet \u0161io atributo reik\u0161m\u0117 tur\u0117t\u0173 b\u016Bti 'ou=users,dc=example,dc=com' +authentication-type=Autentifikacijos tipas +ldap.authentication-type.tooltip=LDAP autentifikacijos tipas. Galimi 'none' (anonimin\u0117 LDAP prieiga) arba 'simple' (Prisijungimo DN + slapta\u017Eodis autentifikacijai) autentifikacijos b\u016Bdai +bind-dn=Prisijungimo DN +ldap.bind-dn.tooltip=LDAP administratoriaus DN (Distinguished Name), kuris turi b\u016Bti naudojamas Keycloak prieiti prie LDAP serverio +bind-credential=Prisijungimo slapta\u017Eodis +ldap.bind-credential.tooltip=LDAP administratoriaus slapta\u017Eodis +test-authentication=Tikrinti autentifikacij\u0105 +custom-user-ldap-filter=Papildomas naudotoj\u0173 LDAP filtras +ldap.custom-user-ldap-filter.tooltip=Papildomas LDAP filtras, kuris turi b\u016Bti naudojamas surast\u0173 naudotoj\u0173 nufiltravimui. Palikite tu\u0161\u010Di\u0105 lauk\u0105 jei papildomas filtravimas nereikalingas. \u012Esitikinkite, kad filtras prasideda '(' ir baigiasi ')' simboliais +search-scope=Paie\u0161kos apimtis +ldap.search-scope.tooltip=Jei pasirinkta vieno lygio paie\u0161ka, tuomet naudotoj\u0173 ie\u0161koma vien tik nurodytame naudotoj\u0173 DN. Kai pasirinkta paie\u0161ka medyje, tuomet naudotoj\u0173 ie\u0161koma visose med\u017Eio \u0161akose. I\u0161samesn\u0117s informacijos ie\u0161kokite LDAP dokumentacij\u0105 +use-truststore-spi=Naudoti rakt\u0173 saugyklos SPI +ldap.use-truststore-spi.tooltip=Nurodykite, kuomet LDAP jungtis naudos standalone.xml/domain.xml sukonfig\u016Bruot\u0105 patikim\u0173 liudijim\u0173/rakt\u0173 saugyklos SPI. 'Visada' rei\u0161kia, kad bus naudojama visada. 'Niekada' rei\u0161kia, kad sukonfig\u016Bruota liudijim\u0173 saugykla nebus naudojama. 'Tik LDAP' rei\u0161kia, kad saugykla bus naudojama tik su LDAP jungtimis. Pastaba: jei standalone.xml/domain.xml nesukonfig\u016Bruotas, tuomet bus naudojama standartin\u0117 Java cacerts arba 'javax.net.ssl.trustStore' parametre nurodyta liudijim\u0173 saugykla. +connection-pooling=Jung\u010Di\u0173 buferizavimas +ldap.connection-pooling.tooltip=Ar Keycloak tur\u0117t\u0173 naudoti jung\u010Di\u0173 telkin\u012F jungiantis prie LDAP serverio? +ldap.pagination.tooltip=Ar LDAP serveris palaiko puslapiavim\u0105? +kerberos-integration=Kerberos intergacija +allow-kerberos-authentication=Leisti Kerberos autentifikacij\u0105 +ldap.allow-kerberos-authentication.tooltip=\u012Egalina HTTP naudotoj\u0173 autentifikacij\u0105 naudojant SPNEGO/Kerberos raktus. Duomenys apie prisijungus\u012F naudotoj\u0105 bus teikiama \u0161io LDAP serverio +use-kerberos-for-password-authentication=Naudoti Kerberos autentifikacijai su slapta\u017Eod\u017Eiu +ldap.use-kerberos-for-password-authentication.tooltip=Ar jungiantis su naudotojo vardu ir slapta\u017Eod\u017Eiu naudoti Kerberos server\u012F vietoj LDAP serverio Directory Service API +batch-size=Paketo dydis +ldap.batch-size.tooltip=Vienos tranzacijos metu \u012F Keycloak importuojam\u0173 LDAP naudotoj\u0173 skai\u010Dius. +ldap.periodic-full-sync.tooltip=Ar \u012Fgalinti periodin\u0119 piln\u0105 LDAP naudotoj\u0173 sinchronizacij\u0105 \u012F Keycloak? +ldap.periodic-changed-users-sync.tooltip=Ar \u012Fgalinti periodin\u0119 naujai registruot\u0173 arba su pakeistais duomenimis LDAP naudotoj\u0173 sinchronizacij\u0105 \u012F Keycloak? +ldap.changed-users-sync-period.tooltip=Intervalas sekund\u0117mis, kas kur\u012F atliekamas periodinis naujai registruot\u0173 arba su pakeistais duomenimis LDAP naudotoj\u0173 sinchronizavimas \u012F Keycloak +user-federation-mappers=Federuoto naudotojo atribut\u0173 atitikmenys +create-user-federation-mapper=Sukurti federuoto naudotojo atributo atitikmen\u012F +add-user-federation-mapper=Prid\u0117ti federuoto naudotojo atributo atitikmen\u012F +provider-name=Teik\u0117jo pavadinimas +no-user-federation-providers-configured=Nesukonfig\u016Bruotas nei vienas naudotoj\u0173 federacijos teik\u0117jas +no-user-storage-providers-configured=Nesukonfig\u016Bruota nei viena naudotoj\u0173 saugykla +add-identity-provider=Prid\u0117ti tapatyb\u0117s teik\u0117j\u0105 +add-identity-provider-link=Prid\u0117ti s\u0105saj\u0105 su tapatyb\u0117s teik\u0117ju +identity-provider=Tapatyb\u0117s teik\u0117jas +identity-provider-user-id=Tapatyb\u0117s teik\u0117jo naudotojo ID +identity-provider-user-id.tooltip=Unikalus, tapatyb\u0117s teik\u0117jo saugomas, naudotojo ID +identity-provider-username=Tapatyb\u0117s teik\u0117jo naudotojo vardas +identity-provider-username.tooltip=Tapatyb\u0117s teik\u0117jo sistemoje saugomas naudotojo vardas +pagination=Puslapiavimas + +browser-flow=Autentifikacijos seka +browser-flow.tooltip=Pasirinkite autentifikacijos nar\u0161ykl\u0117je sek\u0105 +registration-flow=Registracijos seka +registration-flow.tooltip=Pasirinkite registracijos nar\u0161ykl\u0117je sek\u0105. +direct-grant-flow=Tiesiogini\u0173 teisi\u0173 seka +direct-grant-flow.tooltip=Pasirinkite tiesiogini\u0173 teisi\u0173 sek\u0105 (direct grant authentication). +reset-credentials=Prisijungimo duomen\u0173 atk\u016Brimo seka +reset-credentials.tooltip=Pasirinkite prisijungimo duomen\u0173 priminimo nar\u0161ykl\u0117je sek\u0105 +client-authentication=Klient\u0173 autentifikacijos seka +client-authentication.tooltip=Pasirinkite klient\u0173 autentifikacijos sek\u0105. +new=Naujas +copy=Kopijuoti +add-execution=Prid\u0117ti i\u0161imt\u012F +add-flow=Prid\u0117ti sek\u0105 +auth-type=Autentifikacijos tipas +requirement=Privalomumas +config=Konfig\u016Bruoti +no-executions-available=N\u0117ra sukonfig\u016Bruot\u0173 i\u0161im\u010Di\u0173 +authentication-flows=Autentifikacijos sekos +create-authenticator-config=Sukurti autentifikatoriaus konfig\u016Bracij\u0105 +authenticator.alias.tooltip=Konfig\u016Bracijos pavadinimas +otp-type=OTP tipas +time-based=Paremtas laiku +counter-based=Paremtas skaitliuku +otp-type.tooltip='totp' paremtas ribot\u0105 laik\u0105 galiojan\u010Diu vienkartiniu slapta\u017Eod\u017Eiu. 'hotp' - ribot\u0105 kart\u0173 galiojan\u010Diu vienkartiniu slapta\u017Eod\u017Eiu. +otp-hash-algorithm=OTP mai\u0161os algoritmas +otp-hash-algorithm.tooltip=Kuris mai\u0161os algoritmas turi b\u016Bti naudojamas OTP generavimui. +number-of-digits=Skaitmen\u0173 skai\u010Dius +otp.number-of-digits.tooltip=Kiek OTP tur\u0117t\u0173 tur\u0117ti skaitmen\u0173? +look-ahead-window=Neatitikimo langas +otp.look-ahead-window.tooltip=Koks intervalas yra leid\u017Eiamas tuo atveju, kai prieigos rakt\u0173 generatoriaus ir serverio laikai arba skaitliukai nesutampa. +initial-counter=Pradin\u0117 skaitliuko reik\u0161m\u0117 +otp.initial-counter.tooltip=Kokia turi b\u016Bti pradin\u0117 skaitliuko reik\u0161m\u0117? +otp-token-period=OTP rakto galiojimo intervalas +otp-token-period.tooltip=Kiek sekund\u017Eiu galios OTP prieigos raktas? Numatyta reik\u0161m\u0117 30 sekund\u017Ei\u0173. +table-of-password-policies=Slapta\u017Eod\u017Eio taisykli\u0173 lentel\u0117 +add-policy.placeholder=Prid\u0117ti taisykl\u0119... +policy-type=Taisykl\u0117s tipas +policy-value=Taisykl\u0117s reik\u0161m\u0117 +admin-events=Administravimo \u012Fvykiai +admin-events.tooltip=Rodomi srities administravimo \u012Fvykiai susij\u0119 su administratoriaus paskyra, pvz. srities k\u016Brimas. Pasirinkite konfig\u016Bravimo skilt\u012F nor\u0117dami kad \u012Fvykiai b\u016Bt\u0173 saugomi. +login-events=Prisijungimo \u012Fvykiai +filter=Filtruoti +update=Atnaujinti +reset=I\u0161valyti +resource-types=Resurso tipas +operation-types=Veiksmas +select-operations.placeholder=Pasirinkite veiksmus... +resource-path=Resurso kelias +resource-path.tooltip=Filtravimas pagal resurso keli\u0105. Palaikomas pakaitos simbolis '*' atitinkantis vien\u0105 kelio element\u0105 ir '**' daugiau nei vien\u0105 element\u0105. Pavyzd\u017Eiui 'realms/*/clients/asbc' visose sritise randa klient\u0105 su identifikatoriumi 'asbc'. Kitas pavyzdys 'realms/master/**' randa visus veiksmus 'master' srityje. +date-(from)=Data (Nuo) +date-(to)=Data (Iki) +authentication-details=Autentifikacijos informacija +ip-address=IP adresas +time=Laikas +operation-type=Veiksmo tipas +auth=Autentifikacijos informacija +representation=Reprezentacija +register=Registracijos +required-action=Privalomi veiksmai +default-action=Numatytas veiksmas +auth.default-action.tooltip=Jei \u012Fgalintas, tuomet visi nauji naudotojai prival\u0117s atlikti pa\u017Eym\u0117tus veiksmus. +no-required-actions-configured=N\u0117ra nei vieno sukonfig\u016Bruoto privalomo veiksmo +defaults-to-id=Nenurod\u017Eius bus naudojamas identifikatorius +flows=Sekos +bindings=S\u0105ry\u0161iai +required-actions=Privalomi veiksmai +password-policy=Slapta\u017Eod\u017Ei\u0173 taisykl\u0117s +otp-policy=OTP taisykl\u0117s +user-groups=Naudotoj\u0173 grup\u0117s +default-groups=Numatytos grup\u0117s +groups.default-groups.tooltip=Nurodykite grupes, \u012F kurias automati\u0161kai \u012Ftraukiami nauji naudotojai. +cut=I\u0161kirpti +paste=\u012Eklijuoti + +create-group=Sukurti grup\u0119 +create-authenticator-execution=Sukurti autentifikatoriaus veiksmo vykdym\u0105 +create-form-action-execution=Sukurti formos veiksmo vykdym\u0105 +create-top-level-form=Sukurti auk\u0161\u010Diausio lygio form\u0105 +flow.alias.tooltip=\u012Era\u0161ykite sekos rodom\u0105 pavadinim\u0105. +top-level-flow-type=Auk\u0161\u010Diausio lygio sekos tipas +flow.generic=generic +flow.client=client +top-level-flow-type.tooltip=Kokio tipo \u0161i auk\u0161\u010Diausio lygio sritis? 'client' tipas naudojamas klient\u0173 (program\u0173) autentifikacijai. 'generic' naudojamas visais kitais atvejais. +create-execution-flow=Sukurti vykdymo sek\u0105 +flow-type=Sekos tipas +flow.form.type=form +flow-type.tooltip=Kokios r\u016B\u0161ies \u0161i forma? +form-provider=Formos teik\u0117jas +default-groups.tooltip=Naujai sukurti ar u\u017Eregistruoti naudotojai automati\u0161kai priskiriami \u0161ioms grup\u0117ms +select-a-type.placeholder=pasirinkite tip\u0105 +available-groups=Galimos grup\u0117s +available-groups.tooltip=Nurodykite grup\u0119, kuri bus numatytoji. +value=Reik\u0161m\u0117 +table-of-group-members=Grup\u0117s nari\u0173 lentel\u0117 +last-name=Pavard\u0117 +first-name=Vardas +email=El. pa\u0161tas +toggle-navigation=Perjungti navigacij\u0105 +manage-account=Valdyti paskyr\u0105 +sign-out=Atsijungti +server-info=Serverio informacija +resource-not-found=Resuras nerastas... +resource-not-found.instruction=Negalime rasti j\u016Bs\u0173 ie\u0161komo resurso. \u012Esitikinkite, kad \u012Fved\u0117te teising\u0105 URL. +go-to-the-home-page=Eiti \u012F pradin\u012F puslap\u012F » +page-not-found=Puslapis nerastas... +page-not-found.instruction=Negalime rasti j\u016Bs\u0173 ie\u0161komo puslapio. \u012Esitikinkite, kad \u012Fved\u0117te teising\u0105 URL. +events.tooltip=Rodomi srities i\u0161saugoti \u012Fvykiai. Rodomi \u012Fvykiai susij\u0119 su naudotoj\u0173 paskyromis, pavyzd\u017Eiui naudotojo prisijungimas. Nor\u0117dami keisti nustatymus pasirinkite 'Konfig\u016Bruoti' +select-event-types.placeholder=Pasirinkite \u012Fvykiu tipus... +events-config.tooltip=Rodoma naudotoj\u0173 ir administravimo \u012Fvyki\u0173 konfig\u016Bracija. +select-an-action.placeholder=Pasirinkite veiksm\u0105... +event-listeners.tooltip=Nurodykite srities \u012Fvyki\u0173 gav\u0117jus. +login.save-events.tooltip=Jei \u012Fgalinta, tuomet su prisijungimu susij\u0119 veiksmai saugomi duomen\u0173 baz\u0117je ir tampa prieinami per administravimo bei naudotojo paskyros valdymo skydus. +clear-events.tooltip=I\u0161trinti visus \u012Fvykius i\u0161 duomen\u0173 baz\u0117s. +events.expiration.tooltip=Nustato \u012Fvyki\u0173 galiojimo laik\u0105. Nebegaliojantys \u012Fvykiai periodi\u0161kai i\u0161trinami i\u0161 duomen\u0173 baz\u0117s. +admin-events-settings=Administravimo veiksm\u0173 nustatymai +save-events=Saugoti \u012Fvykius +admin.save-events.tooltip=Jei \u012Fgalinta, tuomet administravimo veiksmai saugomi duomen\u0173 baz\u0117je ir tampa prieinami per administravimo valdymo skyd\u0105. +saved-types.tooltip=Nurodykite veiksm\u0173 tipus, kurie tur\u0117t\u0173 b\u016Bti i\u0161saugoti. +include-representation=I\u0161saugoti reprezentacij\u0105 +include-representation.tooltip=I\u0161saugoti kur\u016Bmo ir redagavimo u\u017Eklaus\u0173 JSON reprezentacij\u0105. +clear-admin-events.tooltip=I\u0161trina visus su administravimu susijusius veiksmus i\u0161 duomen\u0173 baz\u0117s. +server-version=Serverio versija +info=Informacija +providers=Teik\u0117jai +server-time=Serverio laikas +server-uptime=Serverio veikimo laikas +memory=Atmintis +total-memory=Viso atminties +free-memory=Laisva atmintis +used-memory=Naudojama atmintis +system=Sistema +current-working-directory=Darbinis katalogas +java-version=Java Version +java-vendor=Java Vendor +java-runtime=Java Runtime +java-vm=Java VM +java-vm-version=Java VM Version +java-home=Java Home +user-name=User Name +user-timezone=User Timezone +user-locale=User Locale +system-encoding=System Encoding +operating-system=Operating System +os-architecture=OS Architecture +spi=SPI +granted-roles=Suteiktos rol\u0117s +granted-protocol-mappers=Suteiktos protokolo atitikmen\u0173 s\u0105sajos +additional-grants=Papildomai suteikta +revoke=At\u0161aukti +new-password=Naujas slapta\u017Eodis +password-confirmation=Pakartotas slapta\u017Eodis +reset-password=Pakeisti slapta\u017Eod\u012F +credentials.temporary.tooltip=Jei \u012Fgalinta, tuomet naudotojas prival\u0117s pasikeisti slapta\u017Eod\u012F sekan\u010Dio prisijungimo metu +remove-totp=\u0160alinti TOTP +credentials.remove-totp.tooltip=\u0160alinti vienkartin\u012F naudotojo slapta\u017Eod\u017Ei\u0173 generatori\u0173. +reset-actions=Atkurti veiksmus +credentials.reset-actions.tooltip=Nurodykite naudotojui el. pa\u0161tu siun\u010Diamus privalomus atlikti veiksmus. 'Patvirtinti el. pa\u0161to adres\u0105' \u012F naudotojo el. pa\u0161to adres\u0105 siun\u010Dia patvirtinimo nuorod\u0105. 'Atnaujinti profilio informacij\u0105' reikalauja naudotojo per\u017Ei\u016Br\u0117ti ir atnaujinti profilio informacij\u0105. 'Atnaujinti slapta\u017Eod\u012F' reikalauja naudotojo pasikeisti slapta\u017Eod\u012F. 'Konfig\u016Bruoti TOTP' reikalauja atnaujinti mobilaus slapta\u017Eod\u017Ei\u0173 generatoriaus konfig\u016Bracij\u0105. +reset-actions-email=Atk\u016Brimo veiksm\u0173 siuntimas +send-email=Si\u0173sti el. pa\u0161to lai\u0161k\u0105 +credentials.reset-actions-email.tooltip=Naudotojui siun\u010Diamas el. pa\u0161to lai\u0161kas su nuorodomis leid\u017Eian\u010Diomis atlikti pasirinktus veiksmus. Naudotojas atidar\u0119s siun\u010Diam\u0105 nuorod\u0105 gal\u0117s atlikti atk\u016Brimo veiksmus. Veism\u0173 atlikimui naudotoj\u0173 nebus reikalaujama prisijungti. Pavyzd\u017Eiui parinkus slapta\u017Eod\u017Eio atk\u016Brimo veiksm\u0105, naudotojas gal\u0117s neprisijung\u0119s nurodyti nauj\u0105 slapta\u017Ed\u012F. +add-user=Prid\u0117ti naudotoj\u0105 +created-at=Suk\u016Brimo data +user-enabled=Naudotojas \u012Fgalintas +user-enabled.tooltip=Ne\u012Fgalintam naudotojai neleid\u017Eiama prisijungti prie sistemos. +user-temporarily-locked=Naudotojas laikinai u\u017Erakintas +user-temporarily-locked.tooltip=Naudotojas laikintai u\u017Erakintas, nes per daug klydo prisijungiant prie sistemos. +unlock-user=Atrakinti naudotoj\u0105 +federation-link=Federacijos s\u0105saja +email-verified=El. pa\u0161tas patvirtintas +email-verified.tooltip=Ar naudotojo el. pa\u0161to adresas yra patvirtintas? +required-user-actions=Privalomi veiksmai naudotojui +required-user-actions.tooltip=Nurodykite kuriuos veiksmus po prisijungimo naudotojas privalo atlikti. 'Patvirtinti el. pa\u0161to adres\u0105' \u012F naudotojo el. pa\u0161to adres\u0105 siun\u010Dia patvirtinimo nuorod\u0105. 'Atnaujinti profilio informacij\u0105' reikalauja naudotojo per\u017Ei\u016Br\u0117ti ir atnaujinti profilio informacij\u0105. 'Atnaujinti slapta\u017Eod\u012F' reikalauja naudotojo pasikeisti slapta\u017Eod\u012F. 'Konfig\u016Bruoti TOTP' reikalauja atnaujinti mobilaus slapta\u017Eod\u017Ei\u0173 generatoriaus konfig\u016Bracij\u0105. +locale=Lokal\u0117 +select-one.placeholder=Pasirinkite... +impersonate=\u012Ek\u016Bnyti +impersonate-user=\u012Ek\u016Bnyti naudotoj\u0105 +impersonate-user.tooltip=Prisijungti kaip \u0161is naudotojas. Jei j\u016Bs\u0173 sritis sutampa su naudotojo sritimi, tuomet j\u016Bs\u0173 sesija bus baigta prie\u0161 prisijungiant \u0161iuo naudotoju. +identity-provider-alias=Tapatyb\u0117s teik\u0117jo pseudonimas +provider-user-id=Teik\u0117jo naudotojo ID +provider-username=Teik\u0117jo naudotojo vardas +no-identity-provider-links-available=N\u0117ra nei vienos tapatyb\u0117s teik\u0117jo s\u0105sajos +group-membership=Naryst\u0117 grup\u0117se +group-membership.tooltip=Visos grup\u0117s, kuri\u0173 narys yra \u0161is naudotojas. Pa\u017Eym\u0117kite grup\u0119 ir paspauskite 'Palikti' nor\u0117dami pa\u0161alinti naudotoj\u0105 i\u0161 grup\u0117s. +leave=Palikti +membership.available-groups.tooltip=Grup\u0117s, \u012F kurias galima \u012Ftraukti naudotoj\u0105. Pa\u017Eym\u0117kite grup\u0119 ir paspauskite \u012Ftraukti. +table-of-realm-users=Srities naudotoj\u0173 s\u0105ra\u0161as +view-all-users=Rodyti visus naudotojus +unlock-users=Atrakinti naudotojus +no-users-available=Naudotoj\u0173 n\u0117ra +users.instruction=\u012Eveskite paie\u0161kos kriterij\u0173 arba paspauskite rodyti visus naudotojus +consents=Sutikimai +started=Prad\u0117ta +logout-all-sessions=Atjungti visas sesijas +logout=Seanso pabaiga +new-name=Naujas pavadinimas +ok=Gerai +attributes=Atributai +role-mappings=Roli\u0173 susiejimas +members=Nariai +details=Detaliau +identity-provider-links=S\u0105sajos su tapatyb\u0117s teik\u0117jais +register-required-action=Registruoti privalom\u0105 atlikti veiksm\u0105 +gender=Lytis +address=Adresas +phone=Telefonas +profile-url=Profilio URL +picture-url=Nuotraukos URL +website=Internetin\u0117 svetain\u0117 +import-keys-and-cert=Importuoti raktus ir sertifikatus +import-keys-and-cert.tooltip=\u012Ekelti kliento rakt\u0173 por\u0105 ir sertifikat\u0105. +upload-keys=\u012Ekelti raktus +download-keys-and-cert=Atsisi\u0173sti raktus ir sertifikat\u0105 +no-value-assigned.placeholder=N\u0117ra priskirtos reik\u0161m\u0117s +remove=\u0160alinti +no-group-members=Grup\u0117 neturi nari\u0173 +temporary=Laikinas +join=Prijungti +event-type=\u012Evykio tipas +events-config=\u012Evyki\u0173 konfig\u016Bracija +event-listeners=\u012Evyki\u0173 gav\u0117jai +login-events-settings=Prisijungimo \u012Fvyki\u0173 nustatymai +clear-events=I\u0161valyti \u012Fvykius +saved-types=Saugomi tipai +clear-admin-events=I\u0161valyti administravimo \u012Fvykius +clear-changes=I\u0161valyti pasikeitimus +error=Klaida + +# Authz +# Authz Common +authz-authorization=Autorizacija +authz-owner=Savininkas +authz-uri=URI +authz-scopes=Taikymo sritys +authz-resource=Resursas +authz-resource-type=Resurso tipas +authz-resources=Resursai +authz-scope=Taikymo sritis +authz-authz-scopes=Autorizacijos taikymo sritys +authz-policies=Taisykl\u0117s +authz-permissions=Leidimai +authz-evaluate=I\u0161bandyti +authz-icon-uri=Ikonos URI +authz-icon-uri.tooltip=Ikonos paveiksliuko URI. +authz-select-scope=Parinkite taikymo srit\u012F +authz-select-resource=Parinkite resurs\u0105 +authz-associated-policies=Susietos taisykl\u0117s +authz-any-resource=Bet kuris resursas +authz-any-scope=Bet kuri taikymo sritis +authz-any-role=Bet kuri rol\u0117 +authz-policy-evaluation=I\u0161bandyti taisykl\u0119 +authz-select-client=Parinkite klient\u0105 +authz-select-user=Parinkite naudotoj\u0105 +authz-entitlements=Teis\u0117s +authz-no-resources=Resurs\u0173 n\u0117ra +authz-result=Rezultatas +authz-authorization-services-enabled=Autorizacija \u012Fgalinta +authz-authorization-services-enabled.tooltip=\u012Egalinti detal\u0173 kliento autorizacijos palaikym\u0105 +authz-required=Privalimas + +# Authz Settings +authz-import-config.tooltip=Importuoti \u0161io resurs\u0173 serverio autorizacijos nustatym\u0173 JSON rinkmen\u0105. + +authz-policy-enforcement-mode=Taisykli\u0173 vykdymo r\u0117\u017Eimas +authz-policy-enforcement-mode.tooltip=Taisykli\u0173 vykdymo r\u0117\u017Eimas nusako kaip turi b\u016Bti tenkinamos autorizacijos u\u017Eklaus\u0173 taisykl\u0117s. 'Taikyti' rei\u0161kia, kad tuo atveju kai n\u0117ra sukonfig\u016Bruota nei viena su resursu susijusi taisykl\u0117, prieiga draud\u017Eiama. 'Liberalus' rei\u0161kia, kad tuo atveju kai n\u0117ra sukonfig\u016Bruota nei viena su resursu susijusi taisykl\u0117, prieiga leid\u017Eiama. 'I\u0161jungta' rei\u0161kia, kad neatliekamas taisykli\u0173 tikrinimas ir prieiga leid\u017Eiama prie vis\u0173 resurs\u0173. +authz-policy-enforcement-mode-enforcing=Taikyti +authz-policy-enforcement-mode-permissive=Liberalus +authz-policy-enforcement-mode-disabled=I\u0161jungta + +authz-remote-resource-management=Nuotolinis resurs\u0173 valdymas +authz-remote-resource-management.tooltip=Ar leid\u017Eiama nuotoliniu b\u016Bdu resurs\u0173 serveriui valdyti resursus? Jei ne\u012Fgalinta, tuomet resursai gali b\u016Bti valdomi tik per \u0161i\u0105 administravimo konsol\u0119. + +authz-export-settings=Eksportuoti nustatymus +authz-export-settings.tooltip=Eksportuoti ir atsisi\u0173sti visus \u0161io resurs\u0173 serverio autorazacijos nustatymus. + +# Authz Resource List +authz-no-resources-available=N\u0117ra galim\u0173 resurs\u0173. +authz-no-scopes-assigned=N\u0117ra susiet\u0173 taikymo sri\u010Di\u0173. +authz-no-type-defined=N\u0117ra nurodyt\u0173 tip\u0173. +authz-no-permission-assigned=Nera susiet\u0173 leidim\u0173. +authz-no-policy-assigned=N\u0117ra susiet\u0173 taisykli\u0173. +authz-create-permission=Sukurti leidim\u0105 + +# Authz Resource Detail +authz-add-resource=Prid\u0117ti resurs\u0105 +authz-resource-name.tooltip=Unikalus resurso vardas. Vardas turi unikaliai identifikuoti resurs\u0105. Naudingas, kuomet ie\u0161koma specifini\u0173 resurs\u0173. +authz-resource-owner.tooltip=\u0160io resurso savininkas. +authz-resource-type.tooltip=\u0160io resurso tipas. Reik\u0161m\u0117 leid\u017Eia sugrupuoti skirtingus resursus turin\u010Dius t\u0105 pat\u012F tip\u0105. +authz-resource-uri.tooltip=URI kuris taip pat gali b\u016Bti naudojamas vienareik\u0161mi\u0161kam resurso identifikavimui. +authz-resource-scopes.tooltip=Su \u0161iuo resursu susietos taikymo sritys. + +# Authz Scope List +authz-add-scope=Pri\u0117ti taikymo srit\u012F +authz-no-scopes-available=N\u0117ra galim\u0173 taikymo sri\u010Di\u0173. + +# Authz Scope Detail +authz-scope-name.tooltip=Unikalus taikymo srities pavadinimas. \u0160is pavadinimas gali vienareik\u0161mi\u0161kai identifikuoti taikymo srit\u012F. Naudingas kuomet ie\u0161koma \u0161ios tam tikros srities. + +# Authz Policy List +authz-all-types=Visi tipai +authz-create-policy=Sukurti taisykl\u0119 +authz-no-policies-available=N\u0117ra galim\u0173 taisykli\u0173. + +# Authz Policy Detail +authz-policy-name.tooltip=\u0160ios taisykl\u0117s pavadinimas. +authz-policy-description.tooltip=\u0160ios taisykl\u0117s apra\u0161ymas. +authz-policy-logic=Logika +authz-policy-logic-positive=Teigiama +authz-policy-logic-negative=Neigiama +authz-policy-logic.tooltip=Logika nurodo kaip turi b\u016Bti tenkinama taisykl\u0117. Jei nurodyta 'Teigiama', tuomet \u0161ios taisykl\u0117s vykdymo metu gautas rezultatas (leisti arba drausti) bus naudojamas sprendinio pri\u0117mimui. Jei nurodyta 'Neigiama', tuomet \u0161ios taisykl\u0117s vykdymo rezultatas bus paneigtas, t.y. leid\u017Eiama taps draud\u017Eiama ir atvirk\u0161\u010Diai. +authz-policy-apply-policy=Pritaikyti taisykl\u0119 +authz-policy-apply-policy.tooltip=Nurodo visas taisykles, kurios turi b\u016Bti \u012Fvertintos \u0161ios taisykl\u0117s ar leidimo taikymo sri\u010Diai. +authz-policy-decision-strategy=Sprendimo strategija +authz-policy-decision-strategy.tooltip=Sprendimo strategija nurodo kaip priimamas galutinis sprendimas, kuomet yra vykdomos visos \u0161io leidimo taisykl\u0117s. 'Pozityvi' rei\u0161kia, kad galutiniam teigiamam sprendimui turi b\u016Bti tenkinama bent viena taisykl\u0117. 'Vienbals\u0117' rei\u0161kia, kad galutiniam teigiamam sprendimui visos taisykl\u0117s turi b\u016Bti teigiamos. 'Daugumos' rei\u0161kia, kad galutinis teigiamas sprendimas bus priimtas tuomet, kai teigiam\u0173 taisykli\u0173 bus daugiau nei neigiam\u0173. Jei teigiam\u0173 ir neigiam\u0173 taisykli\u0173 skai\u010Dius yra vienodas, tuomet galutinis rezultatas bus neigiamas. +authz-policy-decision-strategy-affirmative=Pozityvi +authz-policy-decision-strategy-unanimous=Vienbals\u0117 +authz-policy-decision-strategy-consensus=Daugumos +authz-select-a-policy=Parinkite taisykl\u0119 + +# Authz Role Policy Detail +authz-add-role-policy=Prid\u0117ti rol\u0117s taisykl\u0119 +authz-no-roles-assigned=N\u0117ra susiet\u0173 roli\u0173. +authz-policy-role-realm-roles.tooltip=Nurodo kurios *srities* rol\u0117(s) tenkina \u0161i\u0105 taisykl\u0119. +authz-policy-role-clients.tooltip=Parinkite klien\u0105 nor\u0117dami rodyti tik \u0161io kliento roles. +authz-policy-role-client-roles.tooltip=Nurodo *kliento* rol\u0117(\u012Fs) kurios tenkina \u0161i\u0105 taisykl\u0119. + +# Authz User Policy Detail +authz-add-user-policy=Prid\u0117ti naudotojo taisykl\u0119 +authz-no-users-assigned=N\u0117ra susiet\u0173 naudotoj\u0173. +authz-policy-user-users.tooltip=Nurodo kurie naudotojai tenkina \u0161i\u0105 taisykl\u0119. + +# Authz Time Policy Detail +authz-add-time-policy=Prid\u0117ti laiko taisykl\u0119 +authz-policy-time-not-before.tooltip=Nurodykite laik\u0105 iki kurio \u0161i taisykl\u0117 NETENKINAMA. Teigiamas rezultatas duodamas tik tuo atveju, kuomet dabartin\u0117 data ir laikas yra v\u0117lesn\u0117 arba lygi \u0161iai reik\u0161mei. +authz-policy-time-not-on-after=Ne v\u0117liau +authz-policy-time-not-on-after.tooltip=Nurodykite laik\u0105 po kurio \u0161i taisykl\u0117 NETENKINAMA. Teigiamas rezultatas duodamas tik tuo atveju, kuomet dabartin\u0117 data ir laikas yra ankstesni arba lygi \u0161iai reik\u0161mei. +authz-policy-time-day-month=M\u0117nesio diena +authz-policy-time-day-month.tooltip=Nurodykite m\u0117nesio dien\u0105 iki kurios \u0161i taisykl\u0117 TENKINAMA. U\u017Epild\u017Eius antr\u0105j\u012F laukel\u012F, taisykl\u0117 bus TENKINAMA jei diena patenka \u012F nurodyt\u0105 interval\u0105. Reik\u0161m\u0117s nurodomos imtinai. +authz-policy-time-month=M\u0117nesis +authz-policy-time-month.tooltip=Nurodykite m\u0117nes\u012F iki kurio \u0161i taisykl\u0117 TENKINAMA. U\u017Epild\u017Eius antr\u0105j\u012F laukel\u012F, taisykl\u0117 bus TENKINAMA jei m\u0117nesis patenka \u012F nurodyt\u0105 interval\u0105. Reik\u0161m\u0117s nurodomos imtinai. +authz-policy-time-year=Metai +authz-policy-time-year.tooltip=Nurodykite metus iki kuri\u0173 \u0161i taisykl\u0117 TENKINAMA. U\u017Epild\u017Eius antr\u0105j\u012F laukel\u012F, taisykl\u0117 bus TENKINAMA jei metai patenka \u012F nurodyt\u0105 interval\u0105. Reik\u0161m\u0117s nurodomos imtinai. +authz-policy-time-hour=Valanda +authz-policy-time-hour.tooltip=Nurodykite valand\u0105 iki kurios \u0161i taisykl\u0117 TENKINAMA. U\u017Epild\u017Eius antr\u0105j\u012F laukel\u012F, taisykl\u0117 bus TENKINAMA jei valanda patenka \u012F nurodyt\u0105 interval\u0105. Reik\u0161m\u0117s nurodomos imtinai. +authz-policy-time-minute=Minut\u0117 +authz-policy-time-minute.tooltip=Nurodykite minut\u0119 iki kurios \u0161i taisykl\u0117 TENKINAMA. U\u017Epild\u017Eius antr\u0105j\u012F laukel\u012F, taisykl\u0117 bus TENKINAMA jei minut\u0117 patenka \u012F nurodyt\u0105 interval\u0105. Reik\u0161m\u0117s nurodomos imtinai. + +# Authz Drools Policy Detail +authz-add-drools-policy=Prid\u0117ti Drools taisykl\u0119 +authz-policy-drools-maven-artifact-resolve=I\u0161spr\u0119sti +authz-policy-drools-maven-artifact=Maven taisykl\u0117s artefaktas +authz-policy-drools-maven-artifact.tooltip=Nuoroda \u012F Maven GAV artifakt\u0105 kuriame apra\u0161ytos taisykl\u0117s. Kai tik nurodysite GAV, galite paspausti *I\u0161spr\u0119sti* tam kad \u012Fkelti *Modulis* ir *Sesija* laukus. +authz-policy-drools-module=Modulis +authz-policy-drools-module.tooltip=\u0160ioje taisykl\u0117je naudojamas modulis. Privalote nurodyti modul\u012F tam, kad gal\u0117tum\u0117te pasirinkti specifin\u0119 sesij\u0105 taisykli\u0173 \u012Fk\u0117limui. +authz-policy-drools-session=Sesija +authz-policy-drools-session.tooltip=\u0160ioje taisykl\u0117je naudojama sesija. Sesija teikia taisykles reikalingas \u0161ios taisykl\u0117s vykdymui. +authz-policy-drools-update-period=Atnaujinimo intervalas +authz-policy-drools-update-period.tooltip=Nurodykite laiko interval\u0105, kas kur\u012F turi b\u016Bti ie\u0161koma artefakto atnaujinim\u0173. + +# Authz JS Policy Detail +authz-add-js-policy=Prid\u0117ti JavaScript taisykl\u0119 +authz-policy-js-code=Programinis kodas +authz-policy-js-code.tooltip=JavaScript kodas kuriame apra\u0161ytos \u0161ios taisykl\u0117s s\u0105lygos. + + +# Authz Aggregated Policy Detail +authz-aggregated=Agreguota +authz-add-aggregated-policy=Prid\u0117ti agreguot\u0105 taisykl\u0119 + +# Authz Permission List +authz-no-permissions-available=N\u0117ra galim\u0173 leidim\u0173. + +# Authz Permission Detail +authz-permission-name.tooltip=\u0160io leidimo pavadinimas. +authz-permission-description.tooltip=\u0160io leidimo apra\u0161ymas. + +# Authz Resource Permission Detail +authz-add-resource-permission=Prid\u0117ti resurso leidim\u0105 +authz-permission-resource-apply-to-resource-type=Pritaikyti resurso tipui +authz-permission-resource-apply-to-resource-type.tooltip=Nurodykite ar \u0161is leidimas turi b\u016Bti pritaikomas visiems \u0161io tipo resursams. Jei \u012Fgalinta, tuomet leidimo tikrinimas bus atliekamas visiems nurodyto tipo resursams. +authz-permission-resource-resource.tooltip=Nurodykite, kad \u0161is leidimas turi b\u016Bti taikomas tik tam tikriems resursams. +authz-permission-resource-type.tooltip=Nurodykite, kad \u0161i taisykl\u0117 turi b\u016Bti taikoma visiems \u0161io tipo resursams. + +# Authz Scope Permission Detail +authz-add-scope-permission=Prid\u0117ti taikymo srities leidim\u0105 +authz-permission-scope-resource.tooltip=Pasirinkdami resur\u0105 apribosite taikymo sri\u010Di\u0173 s\u0105ra\u0161\u0105. Jei nepasirinkta, tuomet matysite visas galimas taikymo sritis. +authz-permission-scope-scope.tooltip=Nurodo, kad \u0161is leidimas turi b\u016Bti pritaikytas vienai ar daugiau taikymo sri\u010Di\u0173. + +# Authz Evaluation +authz-evaluation-identity-information=Tapatyb\u0117s informacija +authz-evaluation-identity-information.tooltip=Nurodykite tapatyb\u0117s informacij\u0105, kuri bus naudojama taisykli\u0173 vertinime. +authz-evaluation-client.tooltip=Nurodykite klient\u0105, kuris atlieka autorizacijos u\u017Eklausas. Nei nenurodyta, tuomet autorizacijos u\u017Eklausa bus vertinama naudojant dabartin\u012F klient\u0105. +authz-evaluation-user.tooltip=Nurodykite naudotoj\u0105, kurio vardu atliekamas teisi\u0173 serveryje filtravimas. +authz-evaluation-role.tooltip=Nurodykite pasirinkto naudotojo roles. +authz-evaluation-new=Papildyti u\u017Eklaus\u0105 +authz-evaluation-re-evaluate=Vertinti pakartotinai +authz-evaluation-previous=Prie\u0161 tai buv\u0119s bandymas + +authz-evaluation-contextual-info=Kontekstin\u0117 informacija +authz-evaluation-contextual-info.tooltip=Nurodykite kontekstin\u0119 informacij\u0105, kuri bus naudojama taisykli\u0173 vertinime. +authz-evaluation-contextual-attributes=Kontekstiniai atributai +authz-evaluation-contextual-attributes.tooltip=Galite pateikti vykdymo aplinkos arba vykdymo konteksto atributus. +authz-evaluation-permissions.tooltip=Nurodykite leidimus, kuriems bus taikomos taisykl\u0117s. +authz-evaluation-evaluate=Vertinti +authz-evaluation-any-resource-with-scopes=Bet kuris resursas su \u0161ia taikymo sritimi (sritimis) +authz-evaluation-no-result=Vertinant autorizacijos u\u017Eklaus\u0105 rezultat\u0173 nerasta. Patikrinkite ar egzistuoja resursai ar taikymo sritys susietos su taisykl\u0117mis. +authz-evaluation-no-policies-resource=\u0160iam resursui taisykl\u0117s nerastos. +authz-evaluation-result.tooltip=Leidim\u0173 u\u017Eklausos bendras rezultatas. +authz-evaluation-scopes.tooltip=Leid\u017Eiam\u0173 taikymo sri\u010Di\u0173 s\u0105ra\u0161as. +authz-evaluation-policies.tooltip=Informacija apie vertinime dalyvavusias taisykles ir sprendimus. +authz-evaluation-authorization-data=Atsakymas +authz-evaluation-authorization-data.tooltip=Autorizavimo u\u017Eklausos apdorojimo rezultatas su autorizacijos duomenimis. Rezultatas parodo k\u0105 Keycloak gr\u0105\u017Eina klientui pra\u0161an\u010Diam leidimo. Per\u017Ei\u016Br\u0117kite 'authorization' teigin\u012F su leidimais, kurie buvo suteikti \u0161iai autorizacijos u\u017Eklausai. +authz-show-authorization-data=Rodyti autorizacijos duomenis diff --git a/themes/src/main/resources/theme/base/admin/messages/admin-messages_no.properties b/themes/src/main/resources/theme/base/admin/messages/admin-messages_no.properties new file mode 100644 index 0000000000..574eb9156a --- /dev/null +++ b/themes/src/main/resources/theme/base/admin/messages/admin-messages_no.properties @@ -0,0 +1,1118 @@ +consoleTitle=Keycloak administrasjonskonsoll + +# Common messages +enabled=Aktivert +name=Navn +displayName=Vis navn +displayNameHtml=HTML vis navn +save=Lagre +cancel=Avbryt +onText=P\u00C5 +offText=AV +client=Klient +clients=Klienter +clear=T\u00F8m +selectOne=Velg en... + +true=True +false=False + +endpoints=Endepunkter + +# Realm settings +realm-detail.enabled.tooltip=Brukere og klienter har kun tilgang til et sikkerhetsdomene hvis det er aktivert +realm-detail.oidc-endpoints.tooltip=Viser konfigurasjonen av endepunkter for OpenID Connect +registrationAllowed=Registrering av bruker +registrationAllowed.tooltip=Aktiver/deaktiver registreringssiden. En lenke for registrering vil v\u00E6re synlig p\u00E5 innloggingssiden. +registrationEmailAsUsername=E-postadresse som brukernavn +registrationEmailAsUsername.tooltip=Dersom registreringssiden er aktivert, vil feltet for brukernavn v\u00E6re skjult fra registreringsskjema og e-postadresse vil bli brukt som brukernavn for nye brukere. +editUsernameAllowed=Rediger brukernavn +editUsernameAllowed.tooltip=Dersom aktivert, er feltet for brukernavn redigerbart, ellers kun lesbart. +resetPasswordAllowed=Glemt passord +resetPasswordAllowed.tooltip=Vis en lenke p\u00E5 innloggingssiden som brukere kan klikke p\u00E5 om de har glemt sine innloggingsdetaljer. +rememberMe=Husk meg +rememberMe.tooltip=Vis en avkryssingsboks p\u00E5 innloggingssiden som lar brukere forbli innlogget mellom omstart av nettleser og inntil sesjonen utl\u00F8per. +verifyEmail=Bekreft e-postadresse +verifyEmail.tooltip=Krev at bruker verifiserer sin e-postadresse f\u00F8rste gang de logger inn. +sslRequired=Krev SSL +sslRequired.option.all=Alle foresp\u00F8rsler +sslRequired.option.external=Eksterne foresp\u00F8rsler +sslRequired.option.none=Ingen +sslRequired.tooltip=Kreves HTTPS? 'Ingen' betyr at HTTPS ikke kreves for noen klienters IP-adresse. 'Ekstern foresp\u00F8rsel' betyr at localhost og private IP-adresser kan f\u00E5 tilgang uten HTTPS. 'Alle foresp\u00F8rsler' betyr at HTTPS kreves for alle IP-adresser. +publicKey=Offentlig n\u00F8kkel +privateKey=Privat n\u00F8kkel +gen-new-keys=Generer nye n\u00F8kler +certificate=Sertifikat +host=Vert +smtp-host=SMTP Vert +port=Port +smtp-port=SMTP Port (standard er 25) +from=Fra +sender-email-addr=Senders e-postadresse +enable-ssl=Aktiver SSL +enable-start-tls=Aktiver StartTLS +enable-auth=Aktiver autentisering +username=Brukernavn +login-username=Innloggingsbrukernavn +password=Passord +login-password=Innloggingspassord +login-theme=Innloggingstema +login-theme.tooltip=Velg tema for sidene: innlogging, TOTP, rettigheter, registrering, glemt passord. +account-theme=Kontotema +account-theme.tooltip=Velg tema for brukerkontoadministrasjonssider. +admin-console-theme=Administrasjonskonsolltema +select-theme-admin-console=Velg et tema for administrasjonskonsollen. +email-theme=E-posttema +select-theme-email=Velg tema for e-post sendt av server. +i18n-enabled=Internasjonalisering aktivert +supported-locales=St\u00F8ttede lokaliteter +supported-locales.placeholder=Skriv inn en lokalitet og klikk enter +default-locale=Standard lokalitet +realm-cache-clear=Cache for sikkerhetsdomenet +realm-cache-clear.tooltip=T\u00F8m sikkerhetsdomenecache (Dette vil fjerne oppf\u00F8ringer for alle sikkerhetsdomener) +user-cache-clear=Brukercache +user-cache-clear.tooltip=T\u00F8m brukercache (Dette vil fjerne oppf\u00F8ringer for alle sikkerhetsdomener) +revoke-refresh-token=Fjern refresh token +revoke-refresh-token.tooltip=Hvis aktivert kan refresh token kun bli brukt en gang. Ellers vil refresh tokens kunne bli brukt flere ganger. +sso-session-idle=Inaktiv SSO sesjon +seconds=Sekunder +minutes=Minutter +hours=Timer +days=Dager +sso-session-max=Maksimum SSO sesjon +sso-session-idle.tooltip=Tiden en sesjon er tillatt \u00E5 v\u00E6re inaktiv f\u00F8r den utl\u00F8per. Tokens og nettlesersesjoner vil bli ugyldig n\u00E5r en sesjon utl\u00F8per. +sso-session-max.tooltip=Maksimum tid f\u00F8r en sesjon utl\u00F8per. Tokens og nettlesersesjoner vil bli ugyldig n\u00E5r en sesjon utl\u00F8per. +offline-session-idle=Inaktiv sesjon i frakoblet modus +offline-session-idle.tooltip=Tiden en sesjon i frakoblet modus er tillatt \u00E5 v\u00E6re inaktiv f\u00F8r den utl\u00F8per. Du m\u00E5 bruke tokens for frakoblet modus for \u00E5 oppdatere sesjonen minst en gang i denne perioden, ellers vil sesjonen utl\u00F8pe. +access-token-lifespan=Levetid for access token +access-token-lifespan.tooltip= Maksimum tid f\u00F8r et access token utl\u00F8per. Det anbefales at denne verdien er kort i forhold til SSO timeout. +access-token-lifespan-for-implicit-flow=Access token-levetid for implicit flow +access-token-lifespan-for-implicit-flow.tooltip=Maksimum tid f\u00F8r et access token utstedt under OpenID Connect implicit flow utl\u00F8per. Det anbefales at denne er kortere enn SSO timeout. Det er ingen mulighet til \u00E5 oppdatere tokenet i l\u00F8pet av en implicit flow, derfor er det satt en separat timeout som er forskjellig fra 'Levetid for Access Token'. +client-login-timeout=Timeout av klientinnlogging +client-login-timeout.tooltip=Maksimum tid en klient har for \u00E5 fullf\u00F8re access token protokollen. Dette burde normalt v\u00E6re 1 minutt. +login-timeout=Timeout for innlogging +login-timeout.tooltip=Maksimum tid en bruker har til \u00E5 fullf\u00F8re en innlogging. Det anbefales at denne er relativt lang. 30 minutter eller mer. +login-action-timeout=Timeout for innloggingshandling. +login-action-timeout.tooltip=Maksimum tid en bruker har til \u00E5 fullf\u00F8re handlinger relatert til innlogging, som \u00E5 oppdatere passord eller konfigurere TOTP. Det anbefales at denne er relativt lang. 5 minutter eller mer. +headers=Headere +brute-force-detection=Deteksjon av Brute Force +x-frame-options=Alternativer for X-Frame +x-frame-options-tooltip=Standardverdi hindrer sider fra \u00E5 bli inkludert via non-origin iframes. (Klikk p\u00E5 etikett for mer informasjon) +content-sec-policy=Sikkerhetspolicy for innhold +content-sec-policy-tooltip=Standardverdi hindrer sider fra \u00E5 bli inkludert via non-origin iframes. (Klikk p\u00E5 etikett for mer informasjon) +content-type-options=Alternativer for X-innholdstyper +content-type-options-tooltip=Standardverdi som forhindrer Internet Explorer og Google Chrome fra \u00E5 MIME-sniffe et svar vekk fra den deklarerte innholdstypen (content-type) (Klikk p\u00E5 etikett for mer informasjon) +max-login-failures=Maksimum antall innloggingsfeil +max-login-failures.tooltip=Hvor mange feil f\u00F8r ventetid blir aktivert. +wait-increment=\u00F8kning av ventetid +wait-increment.tooltip=N\u00E5r terskelen for feil er n\u00E5dd, hvor lenge skal brukeren stenges ute? +quick-login-check-millis=Hurtig innlogging - kontroll av millisekunder +quick-login-check-millis.tooltip=Hvis en feil skjer for raskt samtidig, steng brukeren ute. +min-quick-login-wait=Minimum ventetid for hurtig innlogging +min-quick-login-wait.tooltip=Ventetid etter en hurtig innloggingsfeil. +max-wait=Maksimum ventetid +max-wait.tooltip=Maksimum tid en bruker vil v\u00E6re stengt ute. +failure-reset-time=Tid for tilbakestilling av feil. +failure-reset-time.tooltip=N\u00E5r vil teller for feil nullstilles? +realm-tab-login=Innlogging +realm-tab-keys=N\u00F8kler +realm-tab-email=E-post +realm-tab-themes=Tema +realm-tab-cache=Cache +realm-tab-tokens=Tokens +realm-tab-client-initial-access=F\u00F8rste access token +realm-tab-security-defenses=Sikkerhetsmekanismer +realm-tab-general=Generelt +add-realm=Legg til sikkerhetsdomene + +#Session settings +realm-sessions=Sikkerhetsdomenesesjoner +revocation=Oppheving +logout-all=Logg ut alle +active-sessions=Aktive sesjoner +sessions=Sesjoner +not-before=Ikke f\u00F8r +not-before.tooltip=Opphev alle tokens utstedt f\u00F8r denne datoen. +set-to-now=Sett til n\u00E5 +push=Send +push.tooltip=For enhver klient som har en administratorURL, send dem beskjed om den nye opphevingspolicyen. + +#Protocol Mapper +usermodel.prop.label=Egenskap +usermodel.prop.tooltip=Navn p\u00E5 egenskapsmetoden i UserModel-grensesnittet. For eksempel, en verdi av 'e-post' vil referere til metoden UserModel.getEmail(). +usermodel.attr.label=Brukerattributt +usermodel.attr.tooltip=Navn p\u00E5 lagret brukerattributt som er navnet p\u00E5 en attributt innenfor UserModel.attribute map. +userSession.modelNote.label=Brukersesjonsmerknad +userSession.modelNote.tooltip=Navn p\u00E5 lagret brukersesjonsmerknad innenfor UserSessionModel.note map. +multivalued.label=Flere verdier +multivalued.tooltip=Angir om en attributt st\u00F8tter flere verdier. Hvis true, vil listen med alle verdier for dette attributtet bli satt som claims. Hvis false, vil bare den f\u00F8rste verdien bli satt som claim. +selectRole.label=Velg rolle +selectRole.tooltip=Skriv inn rolle i tekstboksen til venstre, eller klikk p\u00E5 denne knappen for \u00E5 bla gjennom og velge rollen du \u00F8nsker. +tokenClaimName.label=Navn p\u00E5 token claim +tokenClaimName.tooltip=Navn p\u00E5 claim som skal legges inn i token. Denne kan v\u00E6re et fullt kvalifisert navn som 'address.street'. I dette tilfellet vil et nestet jsonobjekt bli laget. +jsonType.label=JSON-type for claims +jsonType.tooltip=JSON-type som burde bli brukt for \u00E5 fylle json claimet i tokenet. long, int, boolean og String er gyldige verdier. +includeInIdToken.label=Legg til i ID token +includeInIdToken.tooltip=Burde claim bli lagt til i ID token? +includeInAccessToken.label=Legg til i access token +includeInAccessToken.tooltip=Burde claim bli lagt til i access token? +includeInUserInfo.label=Legg til i brukerinfo +includeInUserInfo.tooltip=Burde claim bli lagt til i brukerinfo? +usermodel.clientRoleMapping.clientId.label=Klient-ID +usermodel.clientRoleMapping.clientId.tooltip=Klient-ID for \u00E5 mappe roller +usermodel.clientRoleMapping.rolePrefix.label=Prefiks for klientrolle +usermodel.clientRoleMapping.rolePrefix.tooltip=Prefiks for hver klientrolle (valgfri). +usermodel.realmRoleMapping.rolePrefix.label=Prefiks for sikkerhetsdomenerolle +usermodel.realmRoleMapping.rolePrefix.tooltip=Prefiks for hver sikkerhetsdomenerolle (valgfri). + +# client details +clients.tooltip=Klienter er betrodde nettleserapplikasjoner og web-tjenester i et sikkerhetsdomene. Disse klientene kan be om en innlogging. Du kan ogs\u00E5 definere klientspesifikke roller. +search.placeholder=S\u00F8k... +create=Opprett +import=Importer +client-id=Klient-ID +base-url=Base URL +actions=Handlinger +not-defined=Ikke definert +edit=Rediger +delete=Slett +no-results=Ingen resultater +no-clients-available=Ingen klienter er tilgjengelige +add-client=Legg til klient +select-file=Velg fil +view-details=Se detaljer +clear-import=T\u00F8m import +client-id.tooltip=Angir ID referert i URI og tokens. For eksempel 'min-klient'. For SAML er dette ogs\u00E5 forventet utgiververdi fra authn-foresp\u00F8rsler +client.name.tooltip=Angir klientnavnet som blir vist. For eksempel, 'Min klient'. St\u00F8tter n\u00F8kler for lokaliserte verdier. For eksempel\: ${my_client} +client.enabled.tooltip=Deaktiverte klienter kan ikke initiere en innlogging eller motta access tokens. +consent-required=Samtykke p\u00E5krevd +consent-required.tooltip=Hvis aktivert m\u00E5 brukere gi samtykke for at klienten skal f\u00E5 tilgang. +client-protocol=Klientprotokoll +client-protocol.tooltip='OpenID connect' tillater klienter \u00E5 verifisere identiteten til sluttbrukeren basert p\u00E5 autentisering utf\u00F8rt av en autorisasjonsserver. 'SAML' aktiverer en web-basert autentisering og autoriseringsscenarier som inkluderer cross-domain single sign-on (SSO) og som bruker security tokens som inneholder assertions for \u00E5 dele informasjon videre. +access-type=Tilgangstype +access-type.tooltip='Confidential' klienter krever en secret for \u00E5 initiere innloggingsprotokoll. 'Public' klienter krever ikke en secret. 'Bearer-only' klienter er webtjenester som aldri initierer en innlogging. +standard-flow-enabled=Standard flow aktivert +standard-flow-enabled.tooltip=Dette aktiverer standard OpenID Connect redirect-basert autentisering med autorisasjonskode. I forhold til OpenID Connect eller OAuth2 spesifikasjoner aktiverer dette st\u00F8tte for 'Authorization Code Flow' for denne klienten. +implicit-flow-enabled=Implicit flow aktivert +implicit-flow-enabled.tooltip=Dette aktiverer st\u00F8tte for OpenID Connect redirect-basert autentisering uten autorisasjonskode. I forhold til OpenID Connect eller OAuth2 spesifikasjoner aktiverer dette st\u00F8tte for 'Implicit Flow' for denne klienten. +direct-access-grants-enabled=Direct access grants aktivert +direct-access-grants-enabled.tooltip=Dette gir st\u00F8tte for Direct Access Grants, som betyr at klienten har tilgang til brukerens brukernavn/passord og kan bytte dette direkte med Keycloak-serveren for access token. I f\u00F8lge OAuth2 spesifikasjonen, aktiverer dette st\u00F8tte for 'Resource Owner Password Credentials Grant' for denne klienten. +service-accounts-enabled=Tjenestekonto aktivert +service-accounts-enabled.tooltip=Lar deg autentisere denne klienten til Keycloak og hente access token dedikert til denne klienten. I f\u00F8lge OAuth2 spesifikasjonen, aktiverer dette st\u00F8tte for 'Client Credentials Grant' for denne klienten. +include-authnstatement=Inkluder AuthnStatement +include-authnstatement.tooltip=Skal et statement som spesifiserer metoden for tidsstempel inng\u00E5 i innloggingssvaret? +sign-documents=Signer dokumenter +sign-documents.tooltip=Skal SAML dokumenter bli signert av sikkerhetsdomenet? +sign-assertions=Signer assertions +sign-assertions.tooltip=Skal assertions i SAML dokumenter bli signert? Denne innstillingen er ikke n\u00F8dvendig hvis et dokument allerede har blitt signert. +signature-algorithm=Signaturalgoritme +signature-algorithm.tooltip=Signaturalgoritmen som brukes for \u00E5 signere et dokument. +canonicalization-method=Kanoniseringsmetode +canonicalization-method.tooltip=Kanoniseringsmetode for XML signaturer. +encrypt-assertions=Krypter assertions +encrypt-assertions.tooltip=Skal SAML assertions bli kryptert med klientens offentlige n\u00F8kkel ved \u00E5 bruke AES? +client-signature-required=Klientens signatur er p\u00E5krevd +client-signature-required.tooltip=Skal klienten signere sine SAML foresp\u00F8rsler og svar? Og skal de valideres? +force-post-binding=Force POST binding +force-post-binding.tooltip=Bruk alltid POST binding for svar. +front-channel-logout=Front channel utlogging +front-channel-logout.tooltip=Hvis satt til true, krever utlogging en redirect i nettleser til klient. Hvis satt til false, vil server utf\u00F8re en bakgrunnskall for utlogging. +force-name-id-format=Force navn-ID format +force-name-id-format.tooltip=Ignorer forespurt format p\u00E5 Navn-ID emnet og bruk den som er konfigurert i administrasjonskonsollen. +name-id-format=Navn-ID format +name-id-format.tooltip=Navn-ID formatet som skal brukes for emnet. +root-url=Root URL +root-url.tooltip=Root URL lagt til relative URLer +valid-redirect-uris=Gyldig redirect URIer +valid-redirect-uris.tooltip=Gyldig URI m\u00F8nster som en nettleser kan redirecte til etter en vellykket innlogging eller utlogging. Enkle jokertegn er tillatt, for eksempel 'http://example.com/*'. Relativ sti kan ogs\u00E5 spesifiseres, for eksempel /my/relative/path/*. Relative stier er relative til klientens root URL, eller hvis ingen er spesifisert brukes root URL for autorisasjonsserveren. For SAML m\u00E5 du sette et gyldig URI m\u00F8nster hvis du er avhengig av at URL for forbrukertjenesten er integrert med foresp\u00F8rselen for p\u00E5logging. +base-url.tooltip=Standard URL som kan brukes n\u00E5r autorisasjonsserveren trenger \u00E5 redirecte eller lenke tilbake til klienten. +admin-url=Admin URL +admin-url.tooltip=URL til administratorgrensesnitt for klienten. Sett denne hvis klienten st\u00F8tter adapter REST API. Dette REST APIet tillater autorisasjonsserveren til \u00E5 sende tilbakekallingsregler og andre administrative oppgaver. Vanligvis er dette satt til klientens base URL. +master-saml-processing-url=Master SAML prosesserings URL +master-saml-processing-url.tooltip=Hvis konfigurert vil denne URLen bli brukt for hver binding til b\u00E5de SPs Assertion Consumer og Single Logout-tjenester. Denne kan bli individuelt overstyrt for hver binding og tjenester i konfigurasjonen for finkornet SAML endepunkt. +idp-sso-url-ref=IDP initiert SSO URL navn +idp-sso-url-ref.tooltip=Navn p\u00E5 URL-fragment som refererer til klienten n\u00E5r du vil gj\u00F8re en IDP initiert SSO. La denne st\u00E5 tom om du \u00F8nsker \u00E5 deaktivere IDP initiert SSO. URLen vil v\u00E6re: {server-root}/realms/{realm}/protocol/saml/clients/{client-url-name} +idp-sso-relay-state=IDP initiert SSO relay state +idp-sso-relay-state.tooltip=Relay state du \u00F8nsker \u00E5 sende med SAML foresp\u00F8rselen n\u00E5r du vil utf\u00F8re en IDP initiert SSO. +web-origins=Web origins +web-origins.tooltip=Tillat CORS origins. For \u00E5 tillate alle origins med gyldig Redirect URIer legg til '+'. For \u00E5 tillate alle origins legg til '*'. +fine-saml-endpoint-conf=Finkornet SAML endepunktskonfigurasjon +fine-saml-endpoint-conf.tooltip=Utvid denne delen til \u00E5 konfigurere n\u00F8yaktige URLer for Assertion Consumer og Single Logout-tjeneste. +assertion-consumer-post-binding-url=Assertion consumer service POST binding URL +assertion-consumer-post-binding-url.tooltip=SAML POST binding URL for klientens assertion customer service (innloggingsrespons). Du kan la denne st\u00E5 tom om du ikke \u00F8nsker en URL for denne bindingen. +assertion-consumer-redirect-binding-url=Assertion Consumer Service redirect binding URL +assertion-consumer-redirect-binding-url.tooltip=SAML redirect for klientens assertion consumer service (innloggingsrespons). Du kan la denne st\u00E5 tom om du ikke \u00F8nsker en URL for denne bindingen. +logout-service-binding-post-url=logout-tjeneste POST binding URL +logout-service-binding-post-url.tooltip=SAML POST binding URL for klientens single logout-tjeneste. Du kan la dette st\u00E5 tomt om du bruker en annen binding. +logout-service-redir-binding-url=Logout-tjeneste redirect binding URL +logout-service-redir-binding-url.tooltip=SAML redirect binding URL for klientens single logout-tjeneste. Du kan la dette st\u00E5 tomt om du bruker en annen binding. + +# client import +import-client=Importer klient +format-option=Formatalternativer +select-format=Velg et format +import-file=Importer fil + +# client tabs +settings=Innstillinger +credentials=Innloggingsdetaljer +saml-keys=SAML n\u00F8kler +roles=Roller +mappers=Mappere +mappers.tooltip=Protokollmappere som utf\u00F8rer endringer av tokens og dokumenter. De kan utf\u00F8re handlinger som \u00E5 mappe brukerdata til protokollclaims, eller bare endre foresp\u00F8rsler som blir sendt mellom klienten og autorisasjonsserver. +scope=Scope +scope.tooltip=Mapping av scope lar deg begrense hvilke rollemappinger som er inkludert i access token som klienten har bedt om. +sessions.tooltip=Viser aktive sesjoner for denne klienten. Tillater deg \u00E5 se hvilke brukere som er aktive og n\u00E5r de logget inn. +offline-access=Frakoblet tilgang +offline-access.tooltip=Viser frakoblede sesjoner for denne klienten. Tillater deg \u00E5 se hvilke brukere som henter tokens for frakoblet modus og n\u00E5r de henter den. For \u00E5 tilbakekalle alle tokens for klienten, g\u00E5 til fanen Opphev og sett verdien for Ikke f\u00F8r til n\u00E5. +clustering=Clustering +installation=Installasjon +installation.tooltip=Verkt\u00F8y for \u00E5 generere ulike konfigurasjonsformater for klientadapter som du kan laste ned eller klippe og lime inn for \u00E5 konfigurere klientene dine. +service-account-roles=Tjenestekonto-roller +service-account-roles.tooltip=Tillater deg \u00E5 autentisere rollemappinger for tjenestekontoen som er dedikert til denne klienten. + +# client credentials +client-authenticator=Klientautentikator +client-authenticator.tooltip=Klientautentikator som blir brukt for \u00E5 autentisere denne klienten mot keycloak-server +certificate.tooltip=Klientsertifikat for \u00E5 validere JWT utstedt av klienten og signert av privatn\u00F8kkel til klient fra din keystore. +publicKey.tooltip=Offentlig n\u00F8kkel for \u00E5 validere JWT utstedt av klient og signert av klientens privatn\u00F8kkel. +no-client-certificate-configured=Ingen klientsertifikat er konfigurert +gen-new-keys-and-cert=Generer nye n\u00F8kler og sertifikater +import-certificate=Importer sertifikat +gen-client-private-key=Generer privatn\u00F8kkel for klient +generate-private-key=Generer privatn\u00F8kkel +archive-format=Arkivformat +archive-format.tooltip=Java keystore eller PKCS12 arkivformat. +key-alias=N\u00F8kkelalias +key-alias.tooltip=Arkiv-alias for din privatn\u00F8kkel og sertifikater. +key-password=N\u00F8kkelpassord +key-password.tooltip=Passord for \u00E5 f\u00E5 tilgang til privatn\u00F8kler i arkivet +store-password=Lagre passord +store-password.tooltip=Passord for \u00E5 f\u00E5 tilgang til arkivet +generate-and-download=Generer og Last ned +client-certificate-import=Import av klientsertifikat +import-client-certificate=Importer klientsertifikat +jwt-import.key-alias.tooltip=Arkiv-alias for sertifikatet ditt. +secret=Secret +regenerate-secret=Regenere secret +registrationAccessToken=Access token for registrering +registrationAccessToken.regenerate=Regenerer access token for registrering +registrationAccessToken.tooltip=Access token for registrering gir klienter tilgang til registreringstjenesten for klienter. +add-role=Legg til rolle +role-name=Rollenavn +composite=Sammensatt +description=Beskrivelse +no-client-roles-available=Ingen klientroller er tilgjengelig +scope-param-required=Scope parameter p\u00E5krevd +scope-param-required.tooltip=Denne rollen vil kun bli gitt hvis parameter for scope med rollenavn blir brukt under foresp\u00F8rsel av autorisasjon/token. +composite-roles=Sammensatte roller +composite-roles.tooltip=N\u00E5r denne rollen er tildelt/ikke tildelt til en bruker, vil hvilken som helst rolle assosiert med denne bli implisitt tildelt/ikke tildelt. +realm-roles=Sikkerhetsdomeneroller +available-roles=Tilgjengelig roller +add-selected=Legg til valgte +associated-roles=Assosierte roller +composite.associated-realm-roles.tooltip=Sikkerhetsdomeneniv\u00E5-rolle knyttet til denne sammensatte rollen. +composite.available-realm-roles.tooltip=Sikkerhetsdomeneniv\u00E5-roller knyttet til denne sammensatte rollen. +remove-selected=Fjern valgte +client-roles=Klientroller +select-client-to-view-roles=Velg klient for \u00E5 se roller for klient +available-roles.tooltip=Roller fra denne klienten som du kan knytte til denne sammensatte rollen. +client.associated-roles.tooltip=Klientrolle assosiert med denne sammensatte rollen. +add-builtin=Legg til Builtin +category=Kategori +type=Type +no-mappers-available=Ingen mappere er tilgjengelig +add-builtin-protocol-mappers=Legg til Builtin protokollmappere +add-builtin-protocol-mapper=Legg til Builtin protokollmapper +scope-mappings=Scopemapping +full-scope-allowed=Tillatt med fullt scope +full-scope-allowed.tooltip=Lar deg \u00E5 deaktivere alle restriksjoner. +scope.available-roles.tooltip=Sikkerhetsdomeneniv\u00E5-roller som kan bli tildelt til scope. +assigned-roles=Tildelte roller +assigned-roles.tooltip=Sikkerhetsdomeneniv\u00E5-roller tildelt til scope. +effective-roles=Effektive roller +realm.effective-roles.tooltip=Tildelte sikkerhetsdomeneniv\u00E5-roller som kan ha blitt arvet fra en sammensatt rolle. +select-client-roles.tooltip=Velg en klient for \u00E5 se roller for klient +assign.available-roles.tooltip=Klientroller som er tilgjengelige til \u00E5 bli tildelt. +client.assigned-roles.tooltip=Tildelte klientroller. +client.effective-roles.tooltip=Tildelte klientroller som kan ha blitt arvet fra en sammensatt rolle. +basic-configuration=Basiskonfigurasjon +node-reregistration-timeout=Timeout for re-registrering av node +node-reregistration-timeout.tooltip=Intervall for \u00E5 angi maksimum tid for registrerte klienters clusternoder for \u00E5 re-registreres. Hvis en clusternode ikke sender re-regisreringsforesp\u00F8rsel til Keycloak innen dette intervallet, vil den bli uregistrert fra Keycloak. +registered-cluster-nodes=Registrerte clusternoder +register-node-manually=Register node manuelt +test-cluster-availability=Test cluster tilgjengelighet +last-registration=Siste registrering +node-host=Nodevert +no-registered-cluster-nodes=Ingen registrerte clusternoder tilgjengelig +cluster-nodes=Clusternoder +add-node=Legg til node +active-sessions.tooltip=Totalt antall aktive brukersesjoner for denne klienten. +show-sessions=Vis sesjoner +show-sessions.tooltip=Advarsel, dette er en potensielt kostbar operasjon avhengig av antall aktive sesjoner. +user=Bruker +from-ip=Fra IP +session-start=Start av sesjon +first-page=F\u00F8rste side +previous-page=Forrige side +next-page=Neste side +client-revoke.not-before.tooltip=Opphev alle token utstedt f\u00F8r denne datoen for denne klienten. +client-revoke.push.tooltip=Hvis administrator URL er konfigurert for denne klienten, dytt denne policyen p\u00E5 denne klienten. +select-a-format=Velg et format +download=Last ned +offline-tokens=Offline tokens +offline-tokens.tooltip=Totalt antall offline tokens for denne klienten. +show-offline-tokens=Vis offline tokens +show-offline-tokens.tooltip=Advarsel, dette er en potensielt kostbar operasjon avhengig av antall offline tokens. +token-issued=Utgitt token +last-access=Sist aksessert +last-refresh=Siste refresh +key-export=Eksporter n\u00F8kkel +key-import=Importer n\u00F8kkel +export-saml-key=Eksporter SAML n\u00F8kkel +import-saml-key=Importer SAML n\u00F8kkel +realm-certificate-alias=Alias for sikkerhetsdomenesertifikat +realm-certificate-alias.tooltip=Sertifikat for sikkerhetsdomenet er ogs\u00E5 lagret i arkivet. Dette er aliaset. +signing-key=Signeringsn\u00F8kkel +saml-signing-key=SAML signeringsn\u00F8kkel +private-key=Privatn\u00F8kkel +generate-new-keys=Generer nye n\u00F8kler +export=Eksporter +encryption-key=Krypteringsn\u00F8kkel +saml-encryption-key.tooltip=SAML krypteringsn\u00F8kkel. +service-accounts=Tjenestekonto-konto +service-account.available-roles.tooltip=Sikkerhetsdomeneniv\u00E5-roller som kan bli tildelt til tjeneste-konto. +service-account.assigned-roles.tooltip=Sikkerhetsdomeneniv\u00E5-roller tildelt service-konto. +service-account-is-not-enabled-for=Tjeneste-konto er ikke aktivert for {{client}} +create-protocol-mappers=Opprett protokollmappere +create-protocol-mapper=Opprett protokollmapper +protocol=Protokoll +protocol.tooltip=Protokoll... +id=ID +mapper.name.tooltip=Navn p\u00E5 mapper. +mapper.consent-required.tooltip=Ved tildeling av midlertidig tilgang m\u00E5 brukeren samtykke til \u00E5 gi denne informasjonen til klienten? +consent-text=Samtykketekst +consent-text.tooltip=Tekst som blir vist p\u00E5 side for samtykke. +mapper-type=Mappertype +mapper-type.tooltip=Type mapper +# realm identity providers +identity-providers=Identitetsleverand\u00F8r +table-of-identity-providers=Liste over identitetsleverand\u00F8rer +add-provider.placeholder=Legg til leverand\u00F8r... +provider=Leverand\u00F8r +gui-order=Rekkef\u00F8lge for brukergrensesnitt +first-broker-login-flow=Flyt for f\u00F8rste innlogging +post-broker-login-flow=Post-p\u00E5loggingsflyt +redirect-uri=Redirect URI +redirect-uri.tooltip=Redirect URI som skal brukes n\u00E5r du konfigurerer identitetsleverand\u00F8ren. +alias=Alias +identity-provider.alias.tooltip=Aliaset identifiserer en identitetsleverand\u00F8r og kan brukes for \u00E5 bygge en redirect uri. +identity-provider.enabled.tooltip=Aktiver/deaktiver denne identitetsleverand\u00F8ren. +authenticate-by-default=Autentiser som standard +identity-provider.authenticate-by-default.tooltip=Indikerer om en leverand\u00F8r burde fors\u00F8kes \u00E5 brukes om standard for autentisering selv om innloggingssiden enda ikke har blitt vist. +store-tokens=Lagre Tokens +identity-provider.store-tokens.tooltip=Aktiver/deaktiver hvis tokens m\u00E5 bli lagret etter at brukere har blitt autentisert. +stored-tokens-readable=Lagrede lesbare tokens +identity-provider.stored-tokens-readable.tooltip=Aktiver/deaktiver hvis nye brukere kan lese lagrede tokens. Dette tildeles broker.read-token rollen. +update-profile-on-first-login=Oppdater profil ved f\u00F8rste innlogging +on=P\u00E5 +on-missing-info=Ved manglende informasjon +off=Av +update-profile-on-first-login.tooltip=Definer vilk\u00E5rene en bruker m\u00E5 oppfylle for \u00E5 oppdatere sin profil ved f\u00F8rste innlogging. +trust-email=Stol p\u00E5 e-post +trust-email.tooltip=Hvis aktivert vil ikke e-post levert av denne leverand\u00F8ren bli verifisert selv om verifisering er aktivert for sikkerhetsdomenet. +gui-order.tooltip=Antall som angir rekkef\u00F8lgen av leverand\u00F8rer i brukergrensesnittet (For eksempel p\u00E5 innloggingssiden). +first-broker-login-flow.tooltip=Alias for autentiseringsflyt, som trigges etter f\u00F8rste innlogging med denne identitetsleverand\u00F8ren. Begrepet 'F\u00F8rste innlogging' betyr at det enn\u00E5 ikke eksisterer en Keycloak-konto koblet til den autentiserte kontoen til identitetsleverand\u00F8ren. +post-broker-login-flow.tooltip=Alias for autentiseringsflyt, som trigges etter hver innlogging med denne identitetsleverand\u00F8ren. Nyttig om man \u00F8nsker tilleggsverifikasjon av hver bruker autentisert med denne identitetsleverand\u00F8ren (for eksempel med engangskode/engangspassord). La denne st\u00E5 tom om du ikke \u00F8nsker at tilleggautentisering skal bli trigget etter en innlogging med denne identitetsleverand\u00F8ren. Merk ogs\u00E5 at implementasjonen av denne autentikatoren m\u00E5 anta at bruker allerede er satt i ClientSession ettersom identitetsleverand\u00F8ren allerede setter det. +openid-connect-config=OpenID Connect konfigurasjon +openid-connect-config.tooltip=OIDC SP og ekstern IDP-konfigurasjon. +authorization-url=Autorisasjons URL +authorization-url.tooltip=Autorisasjons URLen. +token-url=Token URL +token-url.tooltip=Token URLen. +logout-url=Utloggings URL +identity-provider.logout-url.tooltip=Endepunkt for avsluttende sesjon som brukes for \u00E5 logge ut bruker fra ekstern IDP. +backchannel-logout=Backchannel utlogging +backchannel-logout.tooltip=St\u00F8tter ekstern IDP backchannel utlogging? +user-info-url=Brukerinfo URL +user-info-url.tooltip=Brukerinfo URLen. Denne er valgfri. +identity-provider.client-id.tooltip=Klienten eller klientidentifikator registrert hos identitetsleverand\u00F8ren. +client-secret=Klient secret +show-secret=Vis secret +hide-secret=Skjul secret +client-secret.tooltip=Klienten eller klient secret registrert hos identitetsleverand\u00F8ren. +issuer=Utgiver +issuer.tooltip=Identifikator for utgiver av foresp\u00F8rselen. Hvis dette ikke er oppgitt vil ingen validering utf\u00F8res. +default-scopes=Standard Scopes +identity-provider.default-scopes.tooltip=Scopes som sendes n\u00E5r du ber om autorisasjon. Dette kan v\u00E6re en liste med scopes separert med mellomrom. Standard er satt til 'openid'. +prompt=Prompt +unspecified.option=uspesifisert +none.option=Ingen +consent.option=samtykke +login.option=Innlogging +select-account.option=velg_konto (select_account) +prompt.tooltip=Spesifiserer om autorisasjonsserver skal be sluttbruker om re-autentisering og samtykke. +validate-signatures=Valider signaturer +identity-provider.validate-signatures.tooltip=Aktiver/deaktiver signaturvalidering av eksterne IDP signaturer. +validating-public-key=Valider offentlig n\u00F8kkel +identity-provider.validating-public-key.tooltip=PEM format for offentlig n\u00F8kkel som m\u00E5 brukes for \u00E5 kontrollere eksterne IDP signaturer. +import-external-idp-config=Importer ekstern IDP konfigurasjon +import-external-idp-config.tooltip=Lar deg laste inn ekstern IDP metadata fra en konfigurasjonsfil eller ved \u00E5 laste det ned fra en URL. +import-from-url=Importer fra URL +identity-provider.import-from-url.tooltip=Importer metadata fra et eksternt IDP discovery descriptor. +import-from-file=Importer fra fil +identity-provider.import-from-file.tooltip=Importer metadata fra en nedlastet IDP discovery descriptor. +saml-config=SAML konfigurasjon +identity-provider.saml-config.tooltip=SAML SP og ekstern IDP konfigurasjon. +single-signon-service-url=Single sign-on service URL +saml.single-signon-service-url.tooltip=URL som m\u00E5 brukes for \u00E5 sende autentiseringsforesp\u00F8rsler (SAML AuthnRequest). +single-logout-service-url=Single utloggingstjeneste URL +saml.single-logout-service-url.tooltip=URL som m\u00E5 brukes for \u00E5 sende utloggingsforesp\u00F8rsler. +nameid-policy-format=Policy for nameid-format +nameid-policy-format.tooltip=Angir URI-referanse som tilsvarer et format for identifikator-navn. Standard er satt til urn:oasis:names:tc:SAML:2.0:nameid-format:persistent. +http-post-binding-response=HTTP-POST binding svar +http-post-binding-response.tooltip=Indikerer om man skal svare p\u00E5 foresp\u00F8rsler som bruker HTTP-POST binding eller ikke. Hvis satt til false, vil HTTP-REDIRECT binding bli brukt. +http-post-binding-for-authn-request=HTTP-POST binding for AuthnRequest +http-post-binding-for-authn-request.tooltip=Indikerer om AuthnRequests m\u00E5 bli sendt ved \u00E5 bruke en HTTP-POST binding. Hvis satt til false, vil HTTP-REDIRECT binding bli brukt. +want-authn-requests-signed=Vil ha AuthnRequests signert +want-authn-requests-signed.tooltip=Indikerer om identitetsleverand\u00F8r forventer en signert AuthnRequest. +force-authentication=Force autentisering +identity-provider.force-authentication.tooltip=Indikerer om identitetsleverand\u00F8r m\u00E5 autentisere presentat\u00F8ren direkte i stedet for \u00E5 stole p\u00E5 en tidligere sikkerhetskontekst. +validate-signature=Valider signatur +saml.validate-signature.tooltip=Aktiver/deaktiver signaturvalidering av SAML svar. +validating-x509-certificate=Validerer X509 sertifikat +validating-x509-certificate.tooltip=Sertifikatet i PEM format som m\u00E5 brukes for \u00E5 se etter signaturer. +saml.import-from-url.tooltip=Importer metadata fra et eksternt IDP SAML entity descriptor. +social.client-id.tooltip=Identifikator for klient registrert hos identitetsleverand\u00F8r. +social.client-secret.tooltip=Klient secret registrert hos identitetsleverand\u00F8r. +social.default-scopes.tooltip=Scopes som sendes n\u00E5r tillatelse for autorisasjon blir etterspurt. Se dokumentasjon for mulige verdier, separator og standardverdi. +key=N\u00F8kkel +stackoverflow.key.tooltip=N\u00F8kkelen er hentet fra klientregistrering p\u00E5 Stack Overflow. + +# User federation +sync-ldap-roles-to-keycloak=Synkroniser LDAP-roller til Keycloak +sync-keycloak-roles-to-ldap=Synkroniser Keycloak-roller til LDAP +sync-ldap-groups-to-keycloak=Synkroniser LDAP-grupper til Keycloak +sync-keycloak-groups-to-ldap=Synkroniser Keycloak-grupper til LDAP + +realms=Sikkerhetsdomener +realm=Sikkerhetsdomene + +identity-provider-mappers=Identitetsleverand\u00F8rmappere +create-identity-provider-mapper=Opprett identitetsleverand\u00F8rmappere +add-identity-provider-mapper=Legg til identitetsleverand\u00F8rmappere +client.description.tooltip=Angir beskrivelse av klienten. For eksempel\: 'Min klient for timelister'. St\u00F8tter n\u00F8kler for lokaliserte verdier. For eksempel\: ${my_client_description} + +expires=Utl\u00F8per +expiration=Holdbarhet +expiration.tooltip=Angir hvor lenge et token skal v\u00E6re gyldig +count=Teller +count.tooltip=Angir hvor mange klienter som kan bli opprettet ved \u00E5 bruke token. +remainingCount=Resterende antall +created=Opprettet +back=Tilbake +initial-access-tokens=F\u00F8rste access tokens +initial-access-tokens.tooltip=F\u00F8rste access tokens for dynamisk registrering av klienter. Foresp\u00F8rsler med disse tokens kan bli sent fra alle verter. +add-initial-access-tokens=Legg til f\u00F8rste access token +initial-access-token=F\u00F8rste access token +initial-access.copyPaste.tooltip=Kopier/lim inn f\u00F8rste access token f\u00F8r du navigerer vekk fra denne siden ettersom det ikke er mulig \u00E5 hente den senere. +continue=Fortsett +initial-access-token.confirm.title=Kopier f\u00F8rste access token +initial-access-token.confirm.text=Vennligst kopier og lim inn f\u00F8rste access token f\u00F8r du bekrefter ettersom den ikke kan hentes senere +no-initial-access-available=F\u00F8rste access token er ikke tilgjengelig + +trusted-hosts-legend=Betrodde verter for klientregistrering +trusted-hosts-legend.tooltip=Verter, som er betrodd for klientregistrering. klientregistreringsforesp\u00F8rsler fra disse vertene kan bli sent selv uten f\u00F8rste access token. Antall klientregistreringer fra denne spesifikke verten kan bli begrenset til et spesifisert antall. +no-client-trusted-hosts-available=Ingen betrodde verter er tilgjengelig +add-client-reg-trusted-host=Legg til betrodd vert +hostname=Vertsnavn +client-reg-hostname.tooltip=Vertsnavn eller IP-adresse. Klientregistreringsforesp\u00F8rsler fra denne verten/adressen vil bli betrodd og tillat til \u00E5 registrere en ny klient. +client-reg-count.tooltip=Tillat antall klientregistreringsforesp\u00F8rsler fra en spesifikk vert. Du m\u00E5 restarte denne n\u00E5r grensen er n\u00E5dd. +client-reg-remainingCount.tooltip=Gjenv\u00E6rende antall klientregistreringsforesp\u00F8rsler fra denne verten. Du m\u00E5 restarte denne n\u00E5r grensen er n\u00E5dd. +reset-remaining-count=Tilbakestill gjenst\u00E5ende antall + +client-templates=Klientmaler +client-templates.tooltip=Klientmaler tillater deg \u00E5 definere felles konfigurasjon som er delt av flere klienter. + +groups=Grupper + +group.add-selected.tooltip=Sikkerhetsdomene-roller som kan bli tildelt gruppen. +group.assigned-roles.tooltip=Sikkerhetsdomene-roller mappet til gruppen. +group.effective-roles.tooltip=Alle sikkerhetsdomene-rollemappinger. Noen roller kan ha blitt arvet fra en sammensatt rolle. +group.available-roles.tooltip=Roller som kan tildeles fra denne klienten. +group.assigned-roles-client.tooltip=Rollemapping for denne klienten. +group.effective-roles-client.tooltip=Rollemapping for denne klienten. Noen roller kan ha blitt arvet fra en mappet sammensatt rolle. + +default-roles=Standardroller +no-realm-roles-available=Ingen sikkerhetsdomener er tilgjengelig + +users=Brukere +user.add-selected.tooltip=Sikkerhetsdomene-roller som kan bli tildelt bruker. +user.assigned-roles.tooltip=Sikkerhetsdomene-roller mappet til bruker +user.effective-roles.tooltip=Alle sikkerhetsdomene-rollemappinger. Noen roller kan ha blitt arvet fra en sammensatt rolle. +user.available-roles.tooltip=Roller som kan tildeles fra denne klienten. +user.assigned-roles-client.tooltip=Rollemapping for denne klienten. +user.effective-roles-client.tooltip=Rollemapping for denne klienten. Noen roller kan ha blitt arvet fra en mappet sammensatt rolle. +default.available-roles.tooltip=Sikkerhetsdomene-roller som kan tildeles. +realm-default-roles=Standardroller for sikkerhetsdomene +realm-default-roles.tooltip=Sikkerhetsdomene-roller tildelt nye brukere. +default.available-roles-client.tooltip=Roller fra denne klienten som blir tildelt som standard. +client-default-roles=Standard klientroller +client-default-roles.tooltip=Roller fra denne klienten blir tildelt som standardrolle. +composite.available-roles.tooltip=Sikkerhetsdomene-roller knyttet til denne sammensatte rollen. +composite.associated-roles.tooltip=Sikkerhetsdomeneniv\u00E5-roller knyttet til denne sammensatte rollen. +composite.available-roles-client.tooltip=Roller fra denne klienten kan du assosiere med denne sammensatte rollen. +composite.associated-roles-client.tooltip=Klientroller knyttet til denne sammensatte rollen. +partial-import=Delvis import +partial-import.tooltip=Delvis import lar deg importere brukere, klienter og andre ressurser fra en tidligere eksportert json-fil. + +file=File +exported-json-file=Eksporter json-fil +import-from-realm=Importer fra sikkerhetsdomene +import-users=Importer brukere +import-groups=Importer grupper +import-clients=Importer klienter +import-identity-providers=Importer identitetsleverand\u00F8rer +import-realm-roles=Importer roller for sikkerhetsdomene +import-client-roles=Importer klientroller +if-resource-exists=Hvis en ressurs eksisterer +fail=Mislykkes +skip=Hopp over +overwrite=Skriv over +if-resource-exists.tooltip=Spesifiser hva som skal gj\u00F8res om du fors\u00F8ker \u00E5 importere en ressurs som allerede eksisterer. + +action=Handling +role-selector=Rollevelger +realm-roles.tooltip=Rolle for sikkerhetsdomene som kan velges. + +select-a-role=Velg en rolle +select-realm-role=Velg en rolle for sikkerhetsdomenet +client-roles.tooltip=Klientroller som kan velges. +select-client-role=Velg klientrolle + +client-template=Klientmal +client-template.tooltip=Klientmal som denne klienten arver konfigurasjonen fra +client-saml-endpoint=Endepunkt for klient-SAML +add-client-template=Legg til klientmal + +manage=H\u00E5ndter +authentication=Autentisering +user-storage=Brukerlagring +user-federation=Brukerfederering +events=Hendelser +realm-settings=Innstillinger for sikkerhetsdomene +configure=Konfigurer +select-realm=Velg sikkerhetsdomene +add=Legg til + +client-template.name.tooltip=Navn p\u00E5 klientmal. M\u00E5 v\u00E6re unik i sikkerhetsdomenet. +client-template.description.tooltip=Beskrivelse av klientmal +client-template.protocol.tooltip=Hvilken SSO protokoll-konfigurasjon som blir levert av denne klientmalen + +add-user-federation-provider=Legg til leverand\u00F8r for brukerfederering +add-user-storage-provider=Legg til brukerlagringsleverand\u00F8r +required-settings=P\u00E5krevde innstillinger +provider-id=Leverand\u00F8r-ID +console-display-name=Konsoll vis navn +console-display-name.tooltip=Viser navn p\u00E5 leverand\u00F8r n\u00E5r den er lenket i administratorkonsollen. +priority=Prioritet +priority.tooltip=Prioritet av leverand\u00F8r n\u00E5r et oppslag av bruker utf\u00F8res. Lavest f\u00F8rst. +sync-settings=Innstillinger for synkronisering +periodic-full-sync=Fullstendig periodisk synkronisering +periodic-full-sync.tooltip=Skal fullstendig periodisk synkronisering av leverand\u00F8r-brukere til Keycloak v\u00E6re aktivert eller deaktivert +full-sync-period=Fullstendig synkroniseringsperiode +full-sync-period.tooltip=Periode for fullstendig synkronisering i sekunder +periodic-changed-users-sync=Periodisk synkronisering av endrede brukere +periodic-changed-users-sync.tooltip=Skal periodisk synkronisering av endrede eller nylig opprettede leverand\u00F8r-brukere til Keycloak v\u00E6re aktivert eller deaktivert +changed-users-sync-period=Synkroniseringsperiode for endrede brukere +changed-users-sync-period.tooltip=Periode for synkronisering av endrede eller nylig opprettede leverand\u00F8r-brukere i sekunder. +synchronize-changed-users=Synkroniser endrede brukere +synchronize-all-users=Synkroniser alle brukere +kerberos-realm=Sikkerhetsdomene for Kerberos +kerberos-realm.tooltip=Navn p\u00E5 kerberos-sikkerhetsdomene. For eksempel FOO.ORG +server-principal=Server principal +server-principal.tooltip=Fullstendig navn p\u00E5 server principal for HTTP-service som inkluderer server og domenenavn. For eksempel HTTP/host.foo.org@FOO.ORG +keytab=KeyTab +keytab.tooltip=Plassering av Kerberos-keytab fil som inneholder legitimasjonsdetaljer for server principal. For eksempel /etc/krb5.keytab +debug=Feils\u00F8king +debug.tooltip=Aktiver/deaktiver logging av feils\u00F8king til standard output for Krb5LoginModule. +allow-password-authentication=Tillat autentisering med passord +allow-password-authentication.tooltip=Aktiver/deaktivert muligheten for autentisering med brukernavn/passord mot databasen til Kerberos +edit-mode=Redigeringsmodus +edit-mode.tooltip=READ_ONLY betyr at passordoppdateringer ikke er tillatt, og at bruker alltid autentiseres med et Kerberos-passord. UNSYNCED betyr at bruker kan endre sitt passord i databasen til Keycloak og at denne vil bli brukt i stedet for Kerberos-passordet. +ldap.edit-mode.tooltip=READ_ONLY er et skrivebeskyttet LDAP-lager. WRITABLE betyr at data vil bli synkronisert tilbake til LDAP p\u00E5 foresp\u00F8rsel. UNSYNCED betyr at brukerdata vil bli importert, men vil ikke bli synkronisert tilbake til LDAP. +update-profile-first-login=Oppdater profil ved f\u00F8rste innlogging +update-profile-first-login.tooltip=Oppdater profil ved f\u00F8rste innlogging +sync-registrations=Synkroniser registreringer +ldap.sync-registrations.tooltip=Skal nylig opprettede brukere bli opprettet innenfor et LDAP-lager? Prioritet p\u00E5virker hvilken leverand\u00F8r som er valgt for \u00E5 synkronisere den nye brukeren. +vendor=Leverand\u00F8r +ldap.vendor.tooltip=LDAP leverand\u00F8r (provider) +username-ldap-attribute=Brukernavn LDAP-attributt +ldap-attribute-name-for-username=LDAP-attributtnavn for brukernavn +username-ldap-attribute.tooltip=Navn p\u00E5 LDAP-attributt, som er kartlagt som et Keycloak-brukernavn. For mange LDAP-serverleverand\u00F8rer kan dette v\u00E6re 'uid'. For Active directory kan dette v\u00E6re 'sAMAccountName' eller 'cn'. Attributtet burde v\u00E6re fylt for alle LDAP-brukeroppf\u00F8ringer om du vil importere fra LDAP til Keycloak. +rdn-ldap-attribute=RDN LDAP-attributt +ldap-attribute-name-for-user-rdn=LDAP-attributtnavn for RDN-bruker +rdn-ldap-attribute.tooltip=Navn p\u00E5 LDAP-attributt, som brukes som RDN (topp-attributt) for en typisk DN-bruker. Vanligvis er dette det samme som LDAP-attributtet for brukernavn, men det er ikke p\u00E5krevd. For eksempel for Active directory er det vanlig \u00E5 bruke cn' som RDN-attributt n\u00E5r attributtet for brukernavn kan v\u00E6re 'sAMAccountName'. +uuid-ldap-attribute=UUID LDAP-attributt +ldap-attribute-name-for-uuid=LDAP-attributtnavn for UUID. +uuid-ldap-attribute.tooltip=Navn p\u00E5 LDAP-attributtet, som brukes som en unik objekt-identifikator (UUID) for objekter i LDAP. For mange LDAP-serverleverand\u00F8rer er det 'entryUUID', men noen er annerledes. For eksempel for Active directory b\u00F8r det v\u00E6re 'objectGUID'. Hvis din LDAP-server ikke st\u00F8tter UUID, kan du bruke hvilket som helst annet attributt, som er ment \u00E5 v\u00E6re unikt blant LDAP-brukere i treet. For eksempel 'uid' eller 'entryDN'. +user-object-classes=Brukerobjektklasser +ldap-user-object-classes.placeholder=Objektklasser for LDAP-bruker (separert med komma) + +ldap-connection-url=LDAP tilkoblings URL +ldap-users-dn=LDAP brukere DN +ldap-bind-dn=LDAP bind DN +ldap-bind-credentials=LDAP bind innloggingsdetaljer +ldap-filter=LDAP filter +ldap.user-object-classes.tooltip=Alle verdier av LDAP objectClass-attributtet for brukere i LDAP er separert med komma. For eksempel, 'inetOrgPerson, organizationalPerson'. Nylig opprettede Keycloak-brukere vil bli skrevet til LDAP med alle disse objektklassene og eksisterende LDAP-brukeroppf\u00F8ringer vil bli funnet om de inneholder de samme objektklassene. + +connection-url=Tilkoblings URL +ldap.connection-url.tooltip=Tilkoblings URL din til LDAP-server +test-connection=Testkobling +users-dn=DN-brukere +ldap.users-dn.tooltip=Fullstendig DN av LDAP-tre hvor dine brukere befinner seg. Denne spesifikke DN er forelder til LDAP-brukere. Den kan for eksempel v\u00E6re 'ou=users,dc=example,dc=com' hvis din typiske bruker vil ha en DN som 'uid=john,ou=users,dc=example,dc=com' +authentication-type=Autentiseringstype +ldap.authentication-type.tooltip=LDAP Autentiseringstype. For \u00F8yeblikket er kun mekanismene 'ingen' (anonym LDAP autentisering) eller 'enkel' (bind innloggingsdetaljer) tilgjengelig. +bind-dn=Bind DN +ldap.bind-dn.tooltip=DN av LDAP-administrator, som kan brukes av Keycloak for \u00E5 aksessere LDAP-server +bind-credential=Bind innloggingsdetaljer +ldap.bind-credential.tooltip=Passord for LDAP administrator +test-authentication=Testautentisering +custom-user-ldap-filter=Egendefinert filter for LDAP-bruker +ldap.custom-user-ldap-filter.tooltip=TilleggsLDAP-filter for \u00E5 filtrere s\u00F8kte brukere. La filteret v\u00E6re tomt om du ikke trenger et ekstra filter. Pass p\u00E5 at den starter med '(' og slutter med ')' +search-scope=Scope for s\u00F8k +ldap.search-scope.tooltip=For et niv\u00E5 s\u00F8ker vi etter brukere kun i DNser spesifisert av bruker-DNser. For subtre s\u00F8ker vi i hele subtreet. Se LDAP dokumentasjon for mer informasjon. +use-truststore-spi=Bruk Truststore SPI +ldap.use-truststore-spi.tooltip=Spesifiserer om LDAP-koblingen vil bruke truststore SPI med truststore konfigurert i keycloak-server.json. 'Alltid' betyr at den alltid vil brukes. 'Aldri' betyr at den ikke brukes. 'Kun for ldaps' betyr at den vil brukes hvis din koblings URL bruker ldaps. Merk at selv om keycloak-server.json ikke er konfigurert, vil default Java cacerts eller sertifikat spesifisert i 'javax.net.ssl.trustStore' bli brukt. +connection-pooling=Connection Pooling +ldap.connection-pooling.tooltip=Burde Keycloak bruke connection pooling for \u00E5 aksessere LDAP-serveren? +ldap.pagination.tooltip=St\u00F8tter LDAP-serveren paginering? +kerberos-integration=Kerberos Integrasjon +allow-kerberos-authentication=Tillat autentisering med Kerberos +ldap.allow-kerberos-authentication.tooltip=Aktiver/deaktiver HTTP autentisering av brukere med SPNEGO/Kerberos tokens. Informasjonen om autentiserte brukere vil bli klargjort fra denne LDAP-serveren. +use-kerberos-for-password-authentication=Bruk Kerberos for autentisering av passord +ldap.use-kerberos-for-password-authentication.tooltip=Bruk Kerberos-innloggingsmodul for \u00E5 autentisere brukernavn/passord mot Kerberos-server i stedet for autentisering mot LDAP-server med Directory Service API +batch-size=Batch st\u00F8rrelse +ldap.batch-size.tooltip=Antall LDAP-brukere som vil bli importert fra LDAP til Keycloak innen en enkelt transaksjon. +ldap.periodic-full-sync.tooltip=Om fullstendig periodisk synkronisering av LDAP-brukere til Keycloak vil v\u00E6re aktivert eller deaktivert. +ldap.periodic-changed-users-sync.tooltip=Om periodisk synkronisering av endret eller nylig opprettede LDAP-brukere til Keycloak vil v\u00E6re aktivert eller deaktivert. +ldap.changed-users-sync-period.tooltip=Tidsperiode for synkronisering av endrede eller nylig opprettede LDAP-brukere i sekunder. +user-federation-mappers=Mappere for brukerfederering +create-user-federation-mapper=Opprett mapper for brukerfederering +add-user-federation-mapper=Legg til mapper for brukerfederering +provider-name=Leverand\u00F8rnavn +no-user-federation-providers-configured=Ingen leverand\u00F8r for brukerfederering er konfigurert +no-user-storage-providers-configured=Ingen leverand\u00F8r for brukerlagring er konfigurert +add-identity-provider=Legg til identitetsleverand\u00F8r +add-identity-provider-link=Legg til lenke til identitetsleverand\u00F8r +identity-provider=Identitetsleverand\u00F8r +identity-provider-user-id=Bruker-ID for identitetsleverand\u00F8r +identity-provider-user-id.tooltip=Unik ID for brukeren p\u00E5 identitetsleverand\u00F8rens side +identity-provider-username=Brukernavn til identitetsleverand\u00F8r +identity-provider-username.tooltip=Brukernavn p\u00E5 identitetsleverand\u00F8rens side +pagination=Paginering + +browser-flow=Nettleserflyt +browser-flow.tooltip=Velg flyten du \u00F8nsker \u00E5 bruke for nettleser-autentisering. +registration-flow=Registreringsflyt +registration-flow.tooltip=Velg flyten du \u00F8nsker for registrering. +direct-grant-flow=Direct Grant Flyt +direct-grant-flow.tooltip=Velg flyten du \u00F8nsker \u00E5 bruke for direct grant autentisering. +reset-credentials=Tilbakestill innloggingsdetaljer +reset-credentials.tooltip=Velg flyten du \u00F8nsker \u00E5 bruke n\u00E5r brukeren har glemt sine p\u00E5loggingsdetaljer. +client-authentication=Autentisering av klient +client-authentication.tooltip=Velg flyten du \u00F8nsker \u00E5 bruke for autentisering av klienter. +new=Ny +copy=Kopi +add-execution=Legg til eksekvering +add-flow=Legg til flyt +auth-type=Type auth +requirement=Krav +config=Konfig +no-executions-available=Ingen tilgjengelig eksekvering +authentication-flows=Autentiseringsflyt +create-authenticator-config=Opprett konfig for autentikator +authenticator.alias.tooltip=Navn p\u00E5 konfigurasjonen +otp-type=Type engangskode +time-based=Tidsbasert +counter-based=Tellerbasert +otp-type.tooltip=Totp er et tidsbasert engangspassord. 'hotp' er et teller basert engangspassord hvor serveren f\u00F8lger med p\u00E5 en teller som den kan hashe mot. +otp-hash-algorithm=OTP hash-algoritme +otp-hash-algorithm.tooltip=Hva slags hashing algoritme skal brukes for \u00E5 generere OTP. +number-of-digits=Antall siffer +otp.number-of-digits.tooltip=Hvor mange sifre skal OTP ha? +look-ahead-window=Look Ahead Window +otp.look-ahead-window.tooltip=Hvor langt frem b\u00F8r serveren se i tilfelle token generator og server er ute av tidssynkronisering eller tellersynkronisering? +initial-counter=Initiell teller +otp.initial-counter.tooltip=Hva b\u00F8r den initielle tellerverdien v\u00E6re? +otp-token-period=Engangskode token +otp-token-period.tooltip=Hvor mange sekunder burde et engangskode token v\u00E6re gyldig? Standard er satt til 30 sekunder. +table-of-password-policies=Liste over policy for passord +add-policy.placeholder=Legg til policy... +policy-type=Type policy +policy-value=Verdi for policy +admin-events=administratorhendelser +admin-events.tooltip=Viser lagrede administratorhendelser for sikkerhetsdomenet. Hendelser relaterer til administratorkontoen, for eksempel opprettelse av et sikkerhetsdomene. For \u00E5 aktivere persistente hendelser g\u00E5 til konfig. +login-events=innloggingshendelser +filter=Filtrer +update=Oppdater +reset=Tilbakestill +operation-types=Operasjonstyper +resource-types=Ressurstyper +select-operations.placeholder=Velg operasjoner... +select-resource-types.placeholder=Velg ressursyper... +resource-path=Filsti for ressurs +resource-path.tooltip=Sorter etter filsti for ressurs. St\u00F8tter jokertegn '*' for \u00E5 sjekke om den er lik en del av stien, og '**' for \u00E5 sjekke flere deler. For eksempel 'realms/*/clients/asbc' vil v\u00E6re lik klient-ID asbc i alle sikkerhetsdomener, mens 'realms/master/**' er lik alt i mastersikkerhetsdomenet. +date-(from)=Dato (Fra) +date-(to)=Dato (Til) +authentication-details=Autentiseringsdetaljer +ip-address=IP-adresse +time=Tid +operation-type=Operasjonstype +resource-type=Ressurstype +auth=Auth +representation=Representasjon +register=Registrer +required-action=P\u00E5krevd handling +default-action=Standard handling +auth.default-action.tooltip=Hvis aktivert, vil enhver ny bruker bli tilordnet denne p\u00E5krevde handlingen. +no-required-actions-configured=Ingen p\u00E5krevde handlinger er konfigurert +defaults-to-id=Standardverdi er id +flows=Flyt +bindings=Bindinger +required-actions=P\u00E5krevde handlinger +password-policy=Passordpolicy +otp-policy=Policy for engangskode +user-groups=Brukergruppe +default-groups=Standardgrupper +groups.default-groups.tooltip=Sett med grupper som nye brukere automatisk vil bli medlem av. +cut=Klipp +paste=Lim inn + +create-group=Opprett gruppe +create-authenticator-execution=Opprett autentiseringsutf\u00F8relse +create-form-action-execution=Opprett skjema for handlingsutf\u00F8relse +create-top-level-form=Opprett skjema for toppniv\u00E5 +flow.alias.tooltip=Spesifiserer visningsnavn for flyten. +top-level-flow-type=Flytstype for toppniv\u00E5 +flow.generic=generisk +flow.client=klient +top-level-flow-type.tooltip=Hvilken type toppniv\u00E5 flyt er det? Type 'klient' brukes for autentisering av klienter (applikasjoner) n\u00E5r generisk brukes for brukere og alt annet +create-execution-flow=Opprett eksekveringsflyt +flow-type=Type av flyt +flow.form.type=skjema +flow-type.tooltip=Hva slags skjema det er +form-provider=Skjemaleverand\u00F8r +default-groups.tooltip=Nyopprettede eller registrerte brukere vil automatisk bli lagt til disse gruppene +select-a-type.placeholder=velg en type +available-groups=Tilgjengelige grupper +available-groups.tooltip=Velg en gruppe du \u00F8nsker \u00E5 legge til som standard. +value=Verdi +table-of-group-members=Liste over gruppemedlemmer +last-name=Etternavn +first-name=Fornavn +email=E-postadresse +toggle-navigation=Toggle navigasjon +manage-account=Administrer konto +sign-out=Logg ut +server-info=Serverinformasjon +resource-not-found=Ressurs ikke funnet... +resource-not-found.instruction=Vi kunne ikke finne ressursen du leter etter. Vennligst kontroller at nettadressen du oppga er riktig. +go-to-the-home-page=G\u00E5 til hjemmeside » +page-not-found=Side ikke funnet... +page-not-found.instruction=Vi kunne ikke finne siden du ser etter. Vennligst kontroller at nettadressen du skrev inn er riktig. +events.tooltip=Viser lagrede hendelser for sikkerhetsdomenet. Hendelser er relatert til brukerkontoer, for eksempel innlogging av bruker. For \u00E5 aktivere persistente hendelser g\u00E5 til konfig. +select-event-types.placeholder=Velg hendelsestyper... +events-config.tooltip=Viser konfigurasjonsalternativer for \u00E5 muliggj\u00F8re persistente bruker- og administratorhendelser. +select-an-action.placeholder=Velg en handling... +event-listeners.tooltip=Konfigurer hvilke lyttere som skal motta eventer fra sikkerhetsdomenet. +login.save-events.tooltip=Hvis aktivert vil innloggingshendelser bli lagret i databasen, noe som gj\u00F8r hendelsene tilgjengelige for administrator og kontoadministrasjonskonsoll. +clear-events.tooltip=Sletter alle hendelser fra databasen. +events.expiration.tooltip=Setter utl\u00F8pstid for hendelser. Utl\u00F8pte hendelser vil med jevne mellomrom bli slettet fra databasen. +admin-events-settings=Innstillinger for administratorhendelser +save-events=Lagre hendelser +admin.save-events.tooltip=Hvis aktivert vil administratorhendelser bli lagret i databasen, som vil gj\u00F8re hendelsene tilgjengelige i administrasjonskonsollen. +saved-types.tooltip=Konfigurer hvilke eventtyper som lagres. +include-representation=Inkluder representasjon +include-representation.tooltip=Inkluder JSON-representasjon for \u00E5 skape og oppdatere foresp\u00F8rsler. +clear-admin-events.tooltip=Sletter alle administratorhendelser i databasen. +server-version=Serverversjon +info=Info +providers=Leverand\u00F8rer +server-time=Servertid +server-uptime=Oppetid for server +memory=Minne +total-memory=Totalt minne +free-memory=Ledig minne +used-memory=Brukt minne +system=System +current-working-directory=Gjeldende arbeidskatalog +java-version=Java versjon +java-vendor=Java leverand\u00F8r +java-runtime=Java Runtime +java-vm=Java VM +java-vm-version=Java VM versjon +java-home=Java hjem +user-name=Brukers navn +user-timezone=Tidssone for bruker +user-locale=Lokalitet for bruker +system-encoding=Systemenkoding +operating-system=Operativsystem (OS) +os-architecture=OS arkitektur +spi=SPI +granted-roles=Tildelte roller +granted-protocol-mappers=Innvilgede protokollmappere +additional-grants=Tillegsrettigheter +revoke=Opphev +new-password=Nytt passord +password-confirmation=Passord bekreftelse +reset-password=Tilbakestill passord +credentials.temporary.tooltip=Hvis aktivert, er brukeren p\u00E5krevd til \u00E5 endre passordet ved neste innlogging +remove-totp=Fjern TOTP +credentials.remove-totp.tooltip=Fjern generator for engangspassord for bruker. +reset-actions=Tilbakestill handlinger +credentials.reset-actions.tooltip=Sett med handlinger som kan utf\u00F8res ved \u00E5 sende en bruker en Tilbakestillingshandling for E-post. 'Verifiser e-post' sender en e-post til brukeren for \u00E5 verifisere e-postadresse. 'Oppdater profil' krever at bruker legger inn personlig informasjon. 'Oppdater passord' krever at bruker skriver inn et nytt passord. 'Konfigurer TOTP' krever installasjon av en passordgenerator for mobil. +reset-actions-email=Tilbakestillingshandling for E-post. +send-email=Send e-post +credentials.reset-actions-email.tooltip=Sender en e-post til en bruker med en lenke. Ved \u00E5 klikke p\u00E5 denne lenken vil brukeren f\u00E5 lov til \u00E5 utf\u00F8re tilbakestillingshandlinger. Brukeren trenger ikke logge inn f\u00F8r dette. For eksempel, sett handlingen for \u00E5 oppdatere passord, klikk p\u00E5 denne knappen, og brukeren vil kunne endre deres passord uten \u00E5 logge inn. +add-user=Legg til bruker +created-at=Opprettet ved +user-enabled=Bruker aktivert +user-enabled.tooltip=En deaktivert bruker kan ikke logge inn. +user-temporarily-locked=Bruker er midlertidig l\u00E5st. +user-temporarily-locked.tooltip=Brukeren kan ha blitt l\u00E5st p\u00E5 grunn av at innloggingsfors\u00F8k har feilet for mange ganger. +unlock-user=L\u00E5s opp bruker +federation-link=Federeringslenke +email-verified=E-post verifisert +email-verified.tooltip=Har brukerens e-post blitt verifisert? +required-user-actions=P\u00E5krevde brukerhandlinger +required-user-actions.tooltip=Krev en handling n\u00E5r brukeren logger inn. 'Verifiser e-post' sender en e-post til brukeren for \u00E5 verifisere deres e-postadresse. 'Oppdater profil' krever at bruker legger inn personlig informasjon. 'Oppdater passord' krever at bruker skriver inn et nytt passord. 'Konfigurer TOTP' krever installasjon av en passordgenerator for mobil. +locale=Lokalitet +select-one.placeholder=Velg en... +impersonate=Utgi deg for \u00E5 v\u00E6re bruker +impersonate-user=Utgi deg for \u00E5 v\u00E6re bruker +impersonate-user.tooltip=Logg inn som denne brukeren. Hvis bruker er i samme sikkerhetsdomene som deg, vil din n\u00E5v\u00E6rende innloggede sesjon bli logget ut f\u00F8r du blir logget inn som denne brukeren. +identity-provider-alias=Alias for identitetsleverand\u00F8r +provider-user-id=Bruker-ID for leverand\u00F8r +provider-username=Brukernavn for leverand\u00F8r +no-identity-provider-links-available=Ingen lenker for identitetsleverand\u00F8r er tilgjengelig +group-membership=Gruppemedlemskap +leave=Forlat +group-membership.tooltip=Gruppen som brukeren er medlem av. Velg en gruppe p\u00E5 listen og klikk p\u00E5 'Forlat' for \u00E5 forlate gruppen. +membership.available-groups.tooltip=Grupper som brukere kan bli medlem av. Velg en gruppe og klikk p\u00E5 'Bli med' knappen. +table-of-realm-users=Liste over sikkerhetsdomenebrukere +view-all-users=Se alle brukere +unlock-users=L\u00E5s opp brukere +no-users-available=Ingen brukere tilgjengelig +users.instruction=Vennligst skriv inn et s\u00F8k, eller klikk p\u00E5 Se alle brukere +consents=Samtykke +started=Startet +logout-all-sessions=Logg ut av alle sesjoner +logout=Logg ut +new-name=Nytt navn +ok=Ok +attributes=Attributter +role-mappings=Mapping av roller +members=Medlemmer +details=Detaljer +identity-provider-links=Lenker til identitetsleverand\u00F8r +register-required-action=Registrer p\u00E5krevd handling +gender=Kj\u00F8nn +address=Adresse +phone=Telefon +profile-url=Profil URL +picture-url=Bilde URL +website=Nettsted +import-keys-and-cert=Importer n\u00F8kler og sertifikat +import-keys-and-cert.tooltip=Last opp klientens n\u00F8kkelpar og sertifikat. +upload-keys=Last opp n\u00F8kler +download-keys-and-cert=Last ned n\u00F8kler og sertifikat +no-value-assigned.placeholder=Ingen tilordnet verdi +remove=Fjern +no-group-members=Ingen gruppemedlemmer +temporary=Midlertidig +join=Bli med +event-type=Hendelsestype +events-config=Hendelseskonfigurasjon +event-listeners=Hendelseslyttere +login-events-settings=Innstillinger for innloggingshendelser +clear-events=Fjern hendelser +saved-types=Lagrede typer +clear-admin-events=Fjern administratorhendelser +clear-changes=Fjern endringer +error=Feil + +# Authz + # Authz Common +authz-authorization=Autorisasjon +authz-owner=Eier +authz-uri=URI +authz-scopes=Scope +authz-resource=Ressurs +authz-resource-type=Ressurstype +authz-resources=Ressurser +authz-scope=Scope +authz-authz-scopes=Autorisasjonsscopes +authz-policies=Policier +authz-permissions=Tillatelser +authz-evaluate=Evaluer +authz-icon-uri=Ikon URI +authz-icon-uri.tooltip=En URI som peker til et ikon. +authz-select-scope=Velg et scope +authz-select-resource=Velg en ressurs +authz-associated-policies=Assosierte policier +authz-any-resource=Enhver ressurs +authz-any-scope=Ethvert scope +authz-any-role=Enhver rolle +authz-policy-evaluation=Evaluering av policy +authz-select-client=Velg en klient +authz-select-user=Velg en bruker +authz-entitlements=Rettigheter +authz-no-resources=Ingen ressurser +authz-result=Resultat +authz-authorization-services-enabled=Autorisasjon aktivert +authz-authorization-services-enabled.tooltip=Aktiver/deaktiver finkornet autorisasjonssupport for en klient +authz-required=P\u00E5krevd + +# Authz Settings +authz-import-config.tooltip=Importer en JSON-fil som inneholder innstillinger for autorisasjon for denne ressursserveren. + +authz-policy-enforcement-mode=Modus for h\u00E5ndhevelse av policy +authz-policy-enforcement-mode.tooltip=Modus for h\u00E5ndhevelse av policy dikterer hvordan policier blir h\u00E5ndhevet n\u00E5r autorisasjonsforesp\u00F8rsler blir evaluert. 'H\u00E5ndhevende' betyr at foresp\u00F8rsler blir nektet som standard selv om det ikke er en policy knyttet til en gitt ressurs. 'Ettergivende' betyr at foresp\u00F8rsler blir tillatt selv om det ikke er en policy knyttet til en gitt ressurs. 'Deaktivert' deaktiverer fullstendig evalueringen av policier og tillater tilgang til enhver ressurs. +authz-policy-enforcement-mode-enforcing=H\u00E5ndhevende +authz-policy-enforcement-mode-permissive=Ettergivende +authz-policy-enforcement-mode-disabled=Deaktivert + +authz-remote-resource-management=H\u00E5ndtering av ekstern ressurs +authz-remote-resource-management.tooltip=Skal ressursene bli h\u00E5ndtert eksternt av ressursserveren? Hvis satt til false kan ressursene kun bli h\u00E5ndtert fra denne administratorkonsollen. + +authz-export-settings=Eksporter innstillinger +authz-export-settings.tooltip=Eksporter og last ned alle innstillinger for autorisasjon for denne ressursserveren. + + # Authz Resource List +authz-no-resources-available=Ingen tilgjengelige ressurser. +authz-no-scopes-assigned=Ingen tildelte scopes. +authz-no-type-defined=Ingen definert type. +authz-no-permission-assigned=Ingen tillatelse er tildelt. +authz-no-policy-assigned=Ingen tildelt policy. +authz-create-permission=Opprett tillatelse + + # Authz Resource Detail +authz-add-resource=Legg til ressurs +authz-resource-name.tooltip=Et unikt navn for denne ressursen. Navnet kan bli brukt til \u00E5 identifisere en ressurs og er nyttig i sp\u00F8rringer for en bestemt ressurs. +authz-resource-owner.tooltip=Eieren av denne ressursen. +authz-resource-type.tooltip=Ressurstype. Den kan brukes til \u00E5 gruppere ulike ressursinstanser av samme type. +authz-resource-uri.tooltip=En URI som ogs\u00E5 kan brukes for \u00E5 identifisere denne ressursen. +authz-resource-scopes.tooltip=Scopes assosiert med denne ressursen. + + # Authz Scope List +authz-add-scope=Legg til scope +authz-no-scopes-available=Ingen tilgjengelige scopes. + + # Authz Scope Detail +authz-scope-name.tooltip=Et unikt navn for dette scopet. Navnet kan bli brukt for \u00E5 identifisere et scope, og er nyttig i sp\u00F8rringer for en bestemt ressurs. + + # Authz Policy List +authz-all-types=Alle typer +authz-create-policy=Opprett policy +authz-no-policies-available=Ingen tilgjengelige policier. + + # Authz Policy Detail +authz-policy-name.tooltip=Navnet p\u00E5 denne policien. +authz-policy-description.tooltip=En beskrivelse av denne policien. +authz-policy-logic=Logikk +authz-policy-logic-positive=Positiv +authz-policy-logic-negative=Negativ +authz-policy-logic.tooltip=Logikken som dikterer hvordan beslutningspolicien skal utf\u00F8rres. Hvis 'Positiv', vil resulterende effekt (tillate eller nekte) oppn\u00E5dd under evalueringen av denne policien bli brukt for \u00E5 ta en beslutning. Hvis 'Negativ', vil resulterende effekt bli opphevet, med andre ord blir en tillatelse til et avslag og motsatt. +authz-policy-apply-policy=Anvend policy +authz-policy-apply-policy.tooltip=Spesifiserer alle policies som m\u00E5 bli anvendt for scopes definert av denne policien eller tillatelsen. +authz-policy-decision-strategy=Beslutningsstrategi +authz-policy-decision-strategy.tooltip=Beslutningsstrategi som dikterer hvordan policies knyttet til en gitt policy blir evaluert og hvordan endelig avgj\u00F8relse oppn\u00E5s. 'Bekreftende' betyr at minst en policy m\u00E5 evalueres til en positiv beslutning for at den samlede avgj\u00F8relsen kan bli positiv. 'Enstemmig' betyr at alle policies m\u00E5 evalueres til en positiv beslutning for at den samlede avgj\u00F8relsen kan bli positiv. 'Konsensus' betyr at antall positive beslutninger m\u00E5 v\u00E6re h\u00F8yere enn antall negative beslutninger. Hvis antallet av positive og negative er likt, blir den samlede avgj\u00F8relsen negativ. +authz-policy-decision-strategy-affirmative=Bekreftende +authz-policy-decision-strategy-unanimous=Enstemmig +authz-policy-decision-strategy-consensus=Konsensus +authz-select-a-policy=Velg en policy + + # Authz Role Policy Detail +authz-add-role-policy=Legg til policy for rolle +authz-no-roles-assigned=Ingen tildelte roller. +authz-policy-role-roles.tooltip=Spesifiser sikkerhetsdomenerolle(r) som tillates av denne policien. +authz-policy-role-clients.tooltip=Velger en klient for \u00E5 filtrere klientroller som kan bli tatt i bruk av denne policien. +authz-policy-role-client-roles.tooltip=Spesifiserer klientroller tillatt av denne policien. + + # Authz User Policy Detail +authz-add-user-policy=Legg til policy for bruker +authz-no-users-assigned=Ingen tildelte brukere. +authz-policy-user-users.tooltip=Spesifiser bruker(e) som tillates av denne policien. + + # Authz Time Policy Detail +authz-add-time-policy=Legg til policy for tid +authz-policy-time-not-before.tooltip=Definerer tiden f\u00F8r policien M\u00C5 IKKE innvilges. Denne innvilges kun om gjeldende dato/tid er f\u00F8r eller lik denne verdien. +authz-policy-time-not-on-after=Ikke p\u00E5 eller etter +authz-policy-time-not-on-after.tooltip=Definerer tiden etter en policy M\u00C5 IKKE innvilges. Denne innvilges kun om gjeldende dato/tid er f\u00F8r eller lik denne verdien. + + # Authz Drools Policy Detail +authz-add-drools-policy=Legg til Drools policy +authz-policy-drools-maven-artifact-resolve=L\u00F8s +authz-policy-drools-maven-artifact=Policy for Maven artefakt. +authz-policy-drools-maven-artifact.tooltip=Et Maven GAV som peker til et artefakt hvor reglene vil bli lastet fra. Med en gang du har gitt GAV kan du klikke *L\u00F8s* for \u00E5 laste felter for b\u00E5de *Modul* og *Sesjon* +authz-policy-drools-module=Modul +authz-policy-drools-module.tooltip=Modulen som brukes av denne policien. Du m\u00E5 oppgi en modul for \u00E5 velge en bestemt \u00F8kt der reglene vil bli lastet fra. +authz-policy-drools-session=Sesjon +authz-policy-drools-session.tooltip=Sesjonen brukt av denne policien. Sesjonen vil gi alle regler for evaluering ved prosessering av policien. +authz-policy-drools-update-period=Oppdater periode +authz-policy-drools-update-period.tooltip=Spesifiserer et intervall for \u00E5 skanne etter oppdateringer for artefakter. + + # Authz JS Policy Detail +authz-add-js-policy=Legg til policy for JavaScript +authz-policy-js-code=Kode +authz-policy-js-code.tooltip=JavaScript-koden angir betingelsene for denne politikken. + + + # Authz Aggregated Policy Detail +authz-aggregated=Aggregert +authz-add-aggregated-policy=Legg til policy for aggregering. + + # Authz Permission List +authz-no-permissions-available=Ingen tilgjengelige tillatelser. + + # Authz Permission Detail +authz-permission-name.tooltip=Navnet p\u00E5 denne tillatelsen. +authz-permission-description.tooltip=En beskrivelse av denne tillatelsen. + + # Authz Resource Permission Detail +authz-add-resource-permission=Legg til tillatelse for ressurs. +authz-permission-resource-apply-to-resource-type=Bruk p\u00E5 ressurstype +authz-permission-resource-apply-to-resource-type.tooltip=Spesifiserer om denne tillatelsen skal gjelde for alle ressurser med en gitt type. I dette tilfellet vil tillatelsen bli evaluert for alle instanser av gitt ressurstype. +authz-permission-resource-resource.tooltip=Spesifiserer at denne tillatelsen m\u00E5 bli brukt for en spesifikk ressursinstans. +authz-permission-resource-type.tooltip=Spesifiserer at denne tillatelsen m\u00E5 bli anvendt for alle ressursinstanser for en gitt type. + + # Authz Scope Permission Detail +authz-add-scope-permission=Legg til tillatelse for scope +authz-permission-scope-resource.tooltip=Begrens scopes til de som er tilknyttet den valgte ressursen. Hvis dette ikke er valgt vil alle scopes v\u00E6re tilgjengelige. +authz-permission-scope-scope.tooltip=Spesifiserer at denne tillatelse m\u00E5 anvendes p\u00E5 en eller flere scopes. + + # Authz Evaluation +authz-evaluation-identity-information=Identitetsinformasjon +authz-evaluation-identity-information.tooltip=De tilgjengelige alternativene for \u00E5 konfigurere identitesinformasjon som vil bli brukt ved evaluering av policier. +authz-evaluation-client.tooltip=Velg klienten som vil utf\u00F8re denne autorisasjonsforesp\u00F8rselen. +authz-evaluation-user.tooltip=Velg en bruker hvis identitet vil bli brukt for \u00E5 s\u00F8ke tillatelser fra serveren. +authz-evaluation-role.tooltip=Velg en rolle som du vil knytte til den valgte brukeren. +authz-evaluation-new=Ny evaluering +authz-evaluation-re-evaluate=Re-evaluering +authz-evaluation-previous=Forrige evaluering +authz-evaluation-contextual-info=Kontekstuell informasjon +authz-evaluation-contextual-info.tooltip=Tilgjengelige valg for \u00E5 konfigurere enhver kontekstuell informasjon som vil bli brukt ved evaluering av policier. +authz-evaluation-contextual-attributes=Kontekstuelle attributter +authz-evaluation-contextual-attributes.tooltip=Ethvert attributt gitt av et kj\u00F8rende milj\u00F8 eller ved utf\u00F8relseskontekst. +authz-evaluation-permissions.tooltip=De tilgjengelige alternativene for \u00E5 konfigurere tillatelsene for hvilke policies som skal anvendes. +authz-evaluation-evaluate=Evaluer +authz-evaluation-any-resource-with-scopes=Enhver ressurs med scope(s) +authz-evaluation-no-result=Kunne ikke f\u00E5 et resultat for den gitte autorisasjonsforesp\u00F8rselen. Sjekk om de tilgjengelige ressursene er tilknyttet en policy. +authz-evaluation-no-policies-resource=Ingen policies ble funnet for denne ressursen. +authz-evaluation-result.tooltip=Det samlede resultatet for denne foresp\u00F8rselen for tillatelse. +authz-evaluation-scopes.tooltip=Liste over tillatte scopes. +authz-evaluation-policies.tooltip=Detaljer om hvilke policies som ble evaluert og deres avgj\u00F8relser. +authz-evaluation-authorization-data=Respons +authz-evaluation-authorization-data.tooltip=Representerer et token som b\u00E6rer autorisasjonsdata som et resultat av prosesseringen av en autorisasjonsforesp\u00F8rsel. Denne representasjonen er hva Keycloak sender ut til klienter som ettersp\u00F8r tillatelser. Sjekk autorisasjonsclaim for tillatelsene som ble gitt basert p\u00E5 n\u00E5v\u00E6rende autorisasjonsforesp\u00F8rsel. +authz-show-authorization-data=Vis autorisasjonsdata diff --git a/themes/src/main/resources/theme/base/admin/messages/admin-messages_pt_BR.properties b/themes/src/main/resources/theme/base/admin/messages/admin-messages_pt_BR.properties index e69de29bb2..5130658234 100644 --- a/themes/src/main/resources/theme/base/admin/messages/admin-messages_pt_BR.properties +++ b/themes/src/main/resources/theme/base/admin/messages/admin-messages_pt_BR.properties @@ -0,0 +1,1094 @@ +#encoding: utf-8 +consoleTitle=Console de Administração do Keycloak + +# Common messages +enabled=Habilitado +name=Nome +displayName=Nome de exibição +displayNameHtml=Nome de exibição HTML +save=Salvar +cancel=Cancelar +onText=Sim +offText=Não +client=Cliente +clients=Clientes +clear=Limpar +selectOne=Selecione Um... + +true=Sim +false=Não + +endpoints=Endpoints + +# Realm settings +realm-detail.enabled.tooltip=Usuários e clientes somente podem acessar um Realm se ele estiver habilitado +realm-detail.oidc-endpoints.tooltip=Exibe a configuração dos endpoints do OpenID Connect +registrationAllowed=Cadastro de usuário +registrationAllowed.tooltip=Habilita/desabilita a página de cadastro. Um link para a página de cadastro também será exibido na tela de login. +registrationEmailAsUsername=Email como nome de usuário +registrationEmailAsUsername.tooltip=Se habilitado o campo 'nome de usuário' será ocultado no formulário de cadastro e o e-mail será usado como nome de usuário para o novo cadastro. +editUsernameAllowed=Editar nome de usuário +editUsernameAllowed.tooltip=Se habilitado, o campo nome de usuário é editável, senão será apenas leitura. +resetPasswordAllowed=Esqueci a senha +resetPasswordAllowed.tooltip=Exibe um link na página de login para o usuário clicar quando houver esquecido suas credenciais. +rememberMe=Lembrar me +rememberMe.tooltip=Exibe um checkbox na página de login para permitir ao usuário continuar logado entre restarts do browser até que a sessão expire. +verifyEmail=Verificar e-mail +verifyEmail.tooltip=Requer que o usuário verifique seu endereço de e-mail na primeira vez que efetuar login. +sslRequired=SSL requerido +sslRequired.option.all=todas requisições +sslRequired.option.external=requisições externas +sslRequired.option.none=nunca +sslRequired.tooltip=É necessário SSL? 'Nunca' significa que HTTPS não é requerido para nenhum endereço IP cliente. 'Requisições externas' significa que localhost e IPs privados podem acessar sem HTTPS. 'Todas requisições' significa que HTTPS é requerido para todos os endereços IPs. +publicKey=Chave pública +privateKey=Chave privada +gen-new-keys=Gerar novas chaves +certificate=Certificado +host=Host +smtp-host=Host SMTP +port=Porta +smtp-port=Porta SMTP (valor padrão: 25) +from=Remetente +sender-email-addr=Endereço de e-mail do remetente +enable-ssl=Habilitar SSL +enable-start-tls=Habilitar StartTLS +enable-auth=Habilitar autenticação +username=Usuário +login-username=Nome de usuário para login +password=Senha +login-password=Senha para login +login-theme=Tema de login +login-theme.tooltip=Selecione o tema para páginas de login, TOTP, grant, cadastro e recuperar senha. +account-theme=Tema para conta +account-theme.tooltip=Selecione o tema para as páginas de administração de conta do usuário. +admin-console-theme=Tema para console de administração +select-theme-admin-console=Selecione o tema para o console de administração. +email-theme=Tema de e-mail +select-theme-email=Selecione o tema para os e-mail que são enviados pelo servidor. +i18n-enabled=Habilitar internacionalização +supported-locales=Locais disponíveis +supported-locales.placeholder=Digite um local e pressione Enter +default-locale=Local padrão +realm-cache-clear=Realm Cache +realm-cache-clear.tooltip=Remove todas as entradas do cache de realm (isto irá remover as entradas para todos os realms) +user-cache-clear=Cache de usuário +user-cache-clear.tooltip=Remove todas as entradas do cache de usuário (isto irá remover as entradas de todos os realms) +revoke-refresh-token=Revogar Token de Atualização +revoke-refresh-token.tooltip=Se habilitado os tokens de atualização podem ser utilizados somente uma vez. Caso contrário os tokens de atualização não são revogados quando utilizados e podem ser utilizados várias vezes. +sso-session-idle=Sessão SSO inativa +sso-session-idle.tooltip=Tempo que uma sessão pode ficar inativa antes de expirar. Tokens e sessões de navegador são invalidados quando uma sessão é expirada. +seconds=Segundos +minutes=Minutos +hours=Horas +days=Dias +sso-session-max=Sessão SSO Máxima +sso-session-max.tooltip=Tempo máximo antes que uma sessão seja expirada. Tokens e sessões de navegador são invalidados quando uma sessão é expirada. +offline-session-idle=Sessão Offline Inativa +offline-session-idle.tooltip=Tempo que uma sessão offline pode ficar inativa antes de expirar. Você precisa utilizar um token de atualização offline pelo menos uma vez neste período, caso contrário a sessão offline será expirada. +access-token-lifespan=Duração do Token de Acesso +access-token-lifespan.tooltip=Tempo máximo antes que um token de acesso expire. Recomenda-se que este valor seja menor em relação ao tempo de inativação do inativação do SSO. +access-token-lifespan-for-implicit-flow=Duração do token de acesso para fluxos Implícitos +access-token-lifespan-for-implicit-flow.tooltip=Tempo máximo antes que um token de acesso emitido durante o Fluxo Implícito do OpenID Connect expire. Recomenda-se que este valor seja menor em relação ao tempo de inativação do SSO. Não há posibilidade de atualizar este token durante o fluxo implícito, sendo este o motivo de existir um tempo limite diferente para a 'Duração do Token de Acesso'. +client-login-timeout=Tempo limite para login do Cliente +client-login-timeout.tooltip=Tempo máximo que um cliente tem para finalizar o procolo do token de acesso. Normalmente deve ser 1 minuto. +login-timeout=Tempo máximo do Login +login-timeout.tooltip=Tempo máximo que um usuário tempo para completar o login. É recomendado que seja relativamente longo - 30 minutos ou mais. +login-action-timeout=Tempo limite da ação de Login +login-action-timeout.tooltip=Tempo máximo que um usuário tem para completar as ações relacionadas ao login como atualizar senhas ou configurar totp. É recomendado que seja relativamente longo - 5 minutos ou mais. +headers=Cabeçalhos +brute-force-detection=Detecção de ataque de Força Bruta +x-frame-options=X-Frame-Options +x-frame-options-tooltip=O valor padrão impede páginas de serem incluídas via non-origin iframes (clique no label para mais informações) +content-sec-policy=Content-Security-Policy +content-sec-policy-tooltip=O valor padrão impede páginas de serem incluídas via non-origin iframes (clique no label para mais informações) +content-type-options=X-Content-Type-Options +content-type-options-tooltip=O valor padrão impede Internet Explorer and Google Chrome de realizarem MIME-sniffing em uma resposta diferente do content-type declarado (clique no label para mais informações) +max-login-failures=Falhas de login +max-login-failures.tooltip=Quantas falhas de login antes que a espera seja habilitada. +wait-increment=Incremento de Espera +wait-increment.tooltip=Quando a quantidade de falhas for alcançada, quanto tempo o usuário deve aguardar antes de tentar novamente? +quick-login-check-millis=Verificação de Quick Login em Milli Seconds +quick-login-check-millis.tooltip=Se uma falha ocorre concorrentemente neste período, travar a conta do usuário. +min-quick-login-wait=Espera mínima após Quick Login +min-quick-login-wait.tooltip=Quanto tempo aguardar após uma falha de quick login. +max-wait=Espera máxima +max-wait.tooltip=Tempo máximo que um usuário deverá aguardar após uma falha de quick login. +failure-reset-time=Tempo para zerar falhas +failure-reset-time.tooltip=Quando o contador de falhas será resetado? +realm-tab-login=Login +realm-tab-keys=Chaves +realm-tab-email=E-mail +realm-tab-themes=Temas +realm-tab-cache=Cache +realm-tab-tokens=Tokens +realm-tab-client-initial-access=Tokens de Acesso inicial +realm-tab-security-defenses=Defesas +realm-tab-general=Geral +add-realm=Adicionar realm + +#Session settings +realm-sessions=Sessões do Realm +revocation=Revogação +logout-all=Deslogar todos +active-sessions=Sessões Ativas +sessions=Sessões +not-before=Não antes de +not-before.tooltip=Revogar qualquer token emitido antes desta data. +set-to-now=Definir como agora +push=Enviar +push.tooltip=Para cada cliente que possui uma URL de administrador, notificá-los da nova política de revogação. + +#Protocol Mapper +usermodel.prop.label=Propriedade +usermodel.attr.label=Atributo do usuário +userSession.modelNote.label=Nota da sessão de usuário +multivalued.label=Múltiplos valores +selectRole.label=Selecione o Role +tokenClaimName.label=Nome do Token Claim +jsonType.label=Tipo JSON do Claim +includeInIdToken.label=Adicionar ao token de ID +includeInAccessToken.label=Adicionar ao token de acesso +includeInUserInfo.label=Adicionar à informação do usuário +usermodel.clientRoleMapping.clientId.label=ID do cliente +usermodel.clientRoleMapping.rolePrefix.label=Prefixo para o role de Cliente +usermodel.realmRoleMapping.rolePrefix.label=Prefixo do Realm Role + +# client details +search.placeholder=Pesquisar... +create=Criar +import=Importar +client-id=ID do cliente +base-url=URL Base +actions=Ações +not-defined=Não definido +edit=Editar +delete=Excluir +no-results=Sem resultados +no-clients-available=Nenhum cliente disponível +add-client=Adicionar cliente +select-file=Selecionar arquivo +view-details=Ver detalhes +clear-import=Cancelar importação +client-id.tooltip=Especifica o ID referenciado em URI e tokens. Por exemplo 'meu-cliente'. Para SAML também representa o valor do emissor esperado dos authn requests +client.name.tooltip=Especifica o nome de exibição do cliente. Por exemplo 'Meu Cliente'. Também aceita chaves para valores localizados. Por exemplo: ${meu_cliente} +client.description.tooltip=Especifica a descrição do cliente. Por exemplo 'Meu cliente para TimeSheets'. Também aceita chaves para valores localizados. Por exemplo: ${meu_cliente_descricao} +client.enabled.tooltip=Clientes desabilitados não podem realizar login ou obter tokens de acesso. +consent-required=Consentimento exigido +consent-required.tooltip=Se habilitado os usuários devem consentir com o acesso ao cliente. +client-protocol=Protocolo cliente +client-protocol.tooltip='OpenID connect' permite aos Clientes verificarem a identidade do usuário final baseado na autenticação realizada por um servidor de Autorização. 'SAML' permite cenários de autenticação e autorização web-based incluindo cross-domain single sign-on (SSO) e utiliza tokens de segurança contendo assertions para trafegar informações. +access-type=Tipo de acesso +access-type.tooltip=Clientes 'Confidential' requerem um secret para iniciar o protocolo de login. Clientes 'Public' não necessitam de secret. Clientes 'Bearer-only' são web services que nunca iniciam um login. +standard-flow-enabled=Fluxo padrão habilitado +standard-flow-enabled.tooltip=Isto habilita a autenticação baseada em redirecionamento com código de autorização padrão do OpenID Connect. Em termos de especificações OpenID Connect ou OAuth2, isto habilita suporte ao 'Fluxo de Código de Autorização' para este cliente. +implicit-flow-enabled=Fluxo implícito habilitado +implicit-flow-enabled.tooltip=Isto habilita suporte a autenticação baseada em redirecionamento sem código de autorização. Em tempos de especificações OpenID Connect ou OAuth2, isto habilita suporte do 'Fluxo Implícito' para este cliente. +direct-access-grants-enabled=Grants de Acesso direto habilitado +service-accounts-enabled=Contas de serviço habilitadas +include-authnstatement=Incluir AuthnStatement +sign-documents=Assinar documentos +sign-assertions=Assinar assertions +signature-algorithm=Algoritmo de assinatura +canonicalization-method=Método de Canonicalization +encrypt-assertions=Encriptar Assertions +client-signature-required=Assinatura do cliente requerida +force-post-binding=Forçar Binding via POST +front-channel-logout=Front Channel Logout +force-name-id-format=Forçar formato do NameID +name-id-format=Formato do NameID +valid-redirect-uris=URIs de redirecionamento válidas +admin-url=URL do administrador +master-saml-processing-url=URL de processamento SAML principal +idp-sso-url-ref=Nome de URL para SSO iniciado via IDP +idp-sso-relay-state=Estado de relay para SSO iniciado via IDP +fine-saml-endpoint-conf=Configuração de endpoint para configuração fina do SAML +assertion-consumer-post-binding-url=URL para conexão post para o serviço consumidor de Assertions +assertion-consumer-redirect-binding-url=URL para conexão de redirecionamento do serviço consumidor de Assertions +logout-service-post-binding-url=URL de conexão POST para o serviço de logout +logout-service-post-binding-url.tooltip=URL de conexão POST para o serviço de logout +logout-service-redir-binding-url=URL de conexão para o redirecionamento do serviço de logout + +# client import +import-client=Importar cliente +format-option=Formato +import-file=Importar arquivo + +# client tabs +settings=Configurações +credentials=Credenciais +saml-keys=Chaves SAML +roles=Roles +mappers=Mapeamentos +scope=Escopo +offline-access=Acesso offline +installation=Instalação +service-account-roles=Roles de contas de serviço + +# client credentials +client-authenticator=Autenticador do cliente +no-client-certificate-configured=Nenhum certificado cliente configurado +gen-new-keys-and-cert=Gerar novas chaves e certificados +import-certificate=Importar certificado +gen-client-private-key=Gerar chave privada do cliente +generate-private-key=Gerar chave privada +archive-format=Formato do arquivo +key-alias=Alias da chave +key-password=Senha da chave +store-password=Salvar senha +generate-and-download=Gerar e fazer download +client-certificate-import=Importar certificado do cliente +import-client-certificate=Importar certificado do cliente +secret=Segredo +regenerate-secret=Recriar segredo +registrationAccessToken=Token de acesso para registro +registrationAccessToken.regenerate=Regerar token de acesso para registro +add-role=Adicionar Role +role-name=Nome do Role +composite=Composto +description=Descrição +no-client-roles-available=Nenhum role de cliente disponível +scope-param-required=Parâmetro de escopo requerido +composite-roles=Roles compostos +realm-roles=Roles do Realm +available-roles=Roles disponíveis +add-selected=Adicionar selecionados +associated-roles=Roles associados +remove-selected=Remover selecionados +client-roles=Roles de clientes +select-client-to-view-roles=Selecione o cliente para ver os roles do cliente +add-builtin=Adicionar Builtin +category=Categoria +type=Tipo +no-mappers-available=Nenhum mapeamento disponível +add-builtin-protocol-mappers=Adicionar mapeamentos de protocolo Builtin +add-builtin-protocol-mapper=Adicionar mapeamentos de protocolo Builtin + +scope-mappings=Mapeamentos do Escopo +full-scope-allowed=Permitir Escopo completo +assigned-roles=Roles associados +effective-roles=Roles efetivos +basic-configuration=Configuração básica +node-reregistration-timeout=Tempo limite para re-registro de nó +registered-cluster-nodes=Nós de cluster registrados +register-node-manually=Registrar nó manualmente +test-cluster-availability=Testar disponibilidade do cluster +last-registration=Último registro +node-host=Host +no-registered-cluster-nodes=Nenhum nó registrado disponível +cluster-nodes=Nós do cluster +add-node=Adicionar nó +show-sessions=Exibir sessões +user=Usuário +from-ip=Do IP +session-start=Início da sessão +first-page=Primeira página +previous-page=Página anterior +next-page=Próxima página +select-a-format=Selecione um formato +download=Download +offline-tokens=Tokens offline +show-offline-tokens=Exibir tokens offline +token-issued=Token emitido +last-access=Último acesso +last-refresh=Último refresh +key-export=Exportar chave +key-import=Importar chave +export-saml-key=Exportar chave SAML +import-saml-key=Importar chave SAML +realm-certificate-alias=Alias do certificado do Realm +signing-key=Chave de assinatura +saml-signing-key=Chave de assinatura SAML +private-key=Chave privada +generate-new-keys=Gerar novas chaves +export=Exportar +encryption-key=Chave de encriptação +service-accounts=Contas de serviço +service-account-is-not-enabled-for=Contas de serviço não estão habilitadas para {{client}} +create-protocol-mappers=Criar mapeamentos de protocolo +create-protocol-mapper=Criar mapeamento de protocolo +protocol=Protocolo +protocol.tooltip=Protocolo... +id=ID +mapper.name.tooltip=Nome do mapeamento +consent-text=Texto para consentimento +mapper-type=Tipo de mapeamento +# realm identity providers +identity-providers=Provedores de identificação +table-of-identity-providers=Tabela de provedores de identidade +add-provider.placeholder=Adicionar provedor... +provider=Provedor +first-broker-login-flow=Fluxo do primeiro login +post-broker-login-flow=Fluxo pós login +redirect-uri=URI de redirecionamento +alias=Alias +authenticate-by-default=Autenticar por padrão +store-tokens=Salvar Tokens +stored-tokens-readable=Leitura de tokens salvos +trust-email=Confiar no e-mail recebido +gui-order=Ordem na tela +gui-order.tooltip=Número definindo a ordem do provedor na GUI (ex na página de Login). +openid-connect-config=Configuração OpenID Connect +authorization-url=URL de autorização +token-url=URL do Token +logout-url=URL de logout +backchannel-logout=Backchannel Logout +user-info-url=URL de informações do usuário +client-secret=Secret do Cliente +show-secret=Exibir secret +hide-secret=Esconder secret +issuer=Emissor +default-scopes=Escopos padrão +prompt=Prompt +unspecified.option=Não especificado +none.option=Nenhum +consent.option=Consentimento +login.option=Login +select-account.option=select_account +validate-signatures=Validar assinaturas +validating-public-key=Chave pública para validação +import-external-idp-config=Importar configuração de IDP externo +import-from-url=Importar de URL +import-from-file=Importar de arquivo +saml-config=Configuração SAML +single-signon-service-url=URL de serviço do Single Sign On +single-logout-service-url=URL de serviço de Single Logout +nameid-policy-format=Política de formato NameID +http-post-binding-response=Responder com HTTP-POST +http-post-binding-for-authn-request=Utilizar HTTP-POST binding para AuthnRequest +want-authn-requests-signed=Esperar AuthnRequests assinados +force-authentication=Forçar autenticação +validate-signature=Validar assinatura +validating-x509-certificate=Validar certificados X509 +key=Chave + +# User federation +sync-ldap-roles-to-keycloak=Sincronizar os roles do LDAP para o Keycloak +sync-keycloak-roles-to-ldap=Sincronizar os roles do Keycloak para o LDAP +sync-ldap-groups-to-keycloak=Sincronizar os grupos do LDAP para o Keycloak +sync-keycloak-groups-to-ldap=Sincronizar os grupos do Keycloak para o LDAP + +realms=Realms +realm=Realm + +identity-provider-mappers=Mapeamentos de provedores de identificação +create-identity-provider-mapper=Criar mapeamento de provedores de identificação +add-identity-provider-mapper=Adicionar mapeamento de provedor de identificação + +expires=Expira em +expiration=Duração +expiration.tooltip=Especifica por quanto tempo o token deve ser válido +count=Quantidade +count.tooltip=Especifica quantos clientes podem ser criados usando o token +remainingCount=Quantidade restante +created=Criado em +back=Voltar +initial-access-tokens=Tokens de acesso inicial +add-initial-access-tokens=Adicionar token de acesso inicial +initial-access-token=Token de acesso inicial +initial-access.copyPaste.tooltip=Copie/cole o token de acesso inicial antes de sair desta página pois não é possível recuperá-lo depois +continue=Continuar +initial-access-token.confirm.title=Copiar o token de acesso inicial +initial-access-token.confirm.text=Por favor copie e cole o token de acesso inicial antes de confirmar pois não é possível recuperá-lo depois + +client-templates=Modelos de cliente + +groups=Grupos + +default-roles=Roles padrão +no-realm-roles-available=Nenhum role de realm disponível + +users=Usuários +realm-default-roles=Roles padrão do Realm +client-default-roles=Roles padrão do Cliente +partial-import=Importação parcial + +file=Arquivo +exported-json-file=Arquivo json exportado +import-from-realm=Importar de realm +import-users=Importar usuários +import-groups=Importar grupos +import-clients=Importar clientes +import-identity-providers=Importar provedores de identificação +import-realm-roles=Importar roles do realm +import-client-roles=Importar roles de cliente +if-resource-exists=Se um recurso já existir +fail=Falhar +skip=Pular +overwrite=Sobrescrever + +action=Ações +role-selector=Seletor de roles + +select-a-role=Selecione um role +select-realm-role=Selecione um role de realm +select-client-role=Selecione um role de cliente + +client-template=Modelos de Cliente +client-saml-endpoint=Cliente SAML Endpoint +add-client-template=Adicionar modelo de cliente + +manage=Administração +authentication=Autenticação +user-federation=Federação de usuários +events=Eventos +realm-settings=Configurações do Realm +configure=Configuração +select-realm=Selecione um realm +add=Adicionar + + +add-user-federation-provider=Adicionar provedor de federação de usuários +required-settings=Configurações obrigatórias +provider-id=ID do provedor +console-display-name=Nome de exibição no console +priority=Prioridade +sync-settings=Configurações de sincronização +periodic-full-sync=Syncronização completa periódica +full-sync-period=Período +periodic-changed-users-sync=Sincronização periódica de usuários alterados +changed-users-sync-period=Período +synchronize-changed-users=Sincronizar usuários alterados +synchronize-all-users=Sincronizar todos os usuários +kerberos-realm=Realm do Kerberos +server-principal=Principal do servidor +keytab=KeyTab +debug=Debug +allow-password-authentication=Permitir autenticação via senha +edit-mode=Modo de edição +update-profile-first-login=Atualizar Profile no primeiro login +sync-registrations=Sincronizar contas +vendor=Vendor +username-ldap-attribute=Atributo LDAP para Username +ldap-attribute-name-for-username=Atributo LDAP para Username +rdn-ldap-attribute=Atributo LDAP para RDN +ldap-attribute-name-for-user-rdn=Atributo LDAP para RDN +uuid-ldap-attribute=Atributo LDAP para UUID +ldap-attribute-name-for-uuid=Atributo LDAP para UUID +user-object-classes=Classes do objeto User + +ldap-connection-url=URL de conexão ao LDAP +ldap-users-dn=DN dos usuários no LDAP +ldap-bind-dn=DN para bind no LDAP +ldap-bind-credentials=Credenciais para conectar ao LDAP +ldap-filter=Filtro do LDAP + +connection-url=URL de conexão +test-connection=Testar conexão +users-dn=Users DN +authentication-type=Tipo de autenticação +bind-dn=Bind DN +bind-credential=Senha para conexão +test-authentication=Testar autenticação +custom-user-ldap-filter=Filtro de usuários LDAP customizado +search-scope=Escopo de pesquisa +use-truststore-spi=Utilizar Truststore SPI +connection-pooling=Pooling de conexões +kerberos-integration=Integração com Kerberos +allow-kerberos-authentication=Permitir autenticação Kerberos +use-kerberos-for-password-authentication=Utilizar Kerberos para autenticação via senha +batch-size=Tamanho do lote +user-federation-mappers=Mapeamentos de federação de usuário +create-user-federation-mapper=Criar mapeamento de federação de usuário +add-user-federation-mapper=Adicionar mapeamento de federação de usuário +provider-name=Nome do provedor +no-user-federation-providers-configured=Nenhum federação de usuários configurada. +add-identity-provider=Adicionar provedor de identificação +add-identity-provider-link=adicionar link para provedor de identificação +identity-provider=Provedor de identificação +identity-provider-user-id=ID de usuário do provedor de identificação +identity-provider-username=Nome de usuário do provedor de identificação +pagination=Paginação + +browser-flow=Fluxo de browser +registration-flow=Fluxo de registro +direct-grant-flow=Fluxo de Direct Grant +reset-credentials=Reiniciar credenciais +client-authentication=Autenticação do cliente +new=Novo +copy=Copiar +add-execution=Adicionar execução +add-flow=Adicionar fluxo +auth-type=Tipo +requirement=Condição +config=Configuração +no-executions-available=Nenhuma execução disponível +authentication-flows=Fluxos de autenticação +create-authenticator-config=Criar configuração de autenticação +otp-type=Tipo OTP +time-based=Baseado em tempo +counter-based=Baseado em contador +otp-hash-algorithm=Algoritmo de hash OTP +number-of-digits=Quantidade de dígitos +look-ahead-window=Look Ahead Window +initial-counter=Contador inicial +otp-token-period=Período de token OTP +table-of-password-policies=Tabela de política de senhas +add-policy.placeholder=Adicionar política... +policy-type=Tipo da política +policy-value=Valor da política +admin-events=Eventos de adminstração +login-events=Eventos de login +filter=Filtro +update=Atualizar +reset=Reiniciar +operation-types=Tipos de operações +select-operations.placeholder=Selecionar operações... +resource-path=Path do recurso +date-(from)=Data (De) +date-(to)=Data (Até) +authentication-details=Detalhes para autenticação +ip-address=Endereço IP +time=Tempo +operation-type=Tipo de operação +auth=Autenticação +representation=Representação +register=Registro +required-action=Ação requerida +default-action=Ação padrão +no-required-actions-configured=Nenhuma ação requerida configurada +defaults-to-id=ID é o padrão +flows=Fluxos +bindings=Ligações +required-actions=Ações requeridas +password-policy=Política de senha +otp-policy=Política OTP +user-groups=Grupos de usuário +default-groups=Grupos Padrão +cut=Recortar +paste=Colar + +create-group=Criar grupo +create-authenticator-execution=Criar execução de autenticação +create-form-action-execution=Criar execução de ação de formulário +create-top-level-form=Criar formulário de nível superior +top-level-flow-type=Tipo do fluxo de nível superior +flow.generic=genérico +flow.client=cliente +create-execution-flow=Criar fluxo de execução +flow-type=Flow Type +flow.form.type=formulário +flow.generic.type=genérico +form-provider=Provedor de formulário +select-a-type.placeholder=selecione um tipo +available-groups=Grupos disponíveis +value=Valor +table-of-group-members=Tabela de membros do grupo +last-name=Sobrenome +first-name=Primeiro nome +email=E-mail +toggle-navigation=Alternar navegação +manage-account=Administrar a conta +sign-out=Sign Out +server-info=Informação do servidor +resource-not-found=Recurso não encontrado... +resource-not-found.instruction=Não foi possível encontrar o recurso solicitado. Por favor verifique se a URL solicitada está correta. +go-to-the-home-page=Ir para a página inicial » +page-not-found=Página não encontrada... +page-not-found.instruction=Não foi possível encontrar a página solicitada. Por favor verifique se a URL solicitada está correta. +select-event-types.placeholder=Selecione os tipos de eventos... +select-an-action.placeholder=Selecione uma ação... +admin-events-settings=Configuração de eventos de administração +save-events=Salvar eventos +include-representation=Incluir representação +server-version=Versão do servidor +info=Informações +providers=Provedores +server-time=Hora do servidor +server-uptime=Uptime do servidor +memory=Memória +total-memory=Memória total +free-memory=Memória livre +used-memory=Memória utilizada +system=Sistema +current-working-directory=Diretório de trabalho atual +java-version=Versão do Java +java-vendor=Java Vendor +java-runtime=Java Runtime +java-vm=Java VM +java-vm-version=Versão da Java VM +java-home=Java Home +user-name=Usuário +user-timezone=Zona horária do usuário +user-locale=Locale do usuário +system-encoding=Enconding do sistema +operating-system=Sistema operacional +os-architecture=Arquitetura do OS +spi=SPI +granted-roles=Roles concedidos +granted-protocol-mappers=Protocol Mappers concedidos +additional-grants=Concessões adicionais +revoke=Revogar +new-password=Nova senha +password-confirmation=Confirmação de senha +reset-password=Reiniciar senha +remove-totp=Remover TOTP +reset-actions=Ações para reiniciar +reset-actions-email=Ações para reiniciar e-mail +send-email=Enviar e-mail +add-user=Adicionar usuário +created-at=Criado em +user-enabled=Usuário ativo +user-temporarily-locked=Usuário temporariamente desativado +unlock-user=Liberar usuário +federation-link=Link para federação +email-verified=E-mail verificado +required-user-actions=Ações necessárias do usuário +locale=Locale +select-one.placeholder=Selecione um... +impersonate=Personificar +impersonate-user=Personificar usuário +identity-provider-alias=Alias do Provedor de Identificação +provider-user-id=Provider User ID +provider-username=Provider Username +no-identity-provider-links-available=Nenhum link para provedor de identificação disponível +group-membership=Grupos associados +leave=Sair +table-of-realm-users=Tabela de usuários do Realm +view-all-users=Exibir todos os usuários +unlock-users=Liberar usuários +no-users-available=Nenhum usuário disponível +users.instruction=Por favor faça uma pesquisa, ou clique em Exibir todos os usuários +consents=Consentimentos +started=Iniciado +logout-all-sessions=Logout todas as sessões +logout=Logout +new-name=Novo nome +ok=Ok +attributes=Atributos +role-mappings=Mapeamento de roles +members=Membros +details=Detalhes +identity-provider-links=Links de provedores de identificação. +register-required-action=Registrar ação necessária +gender=Gênero +address=Endereço +phone=Telefone +profile-url=URL do perfil +picture-url=URL da foto +website=Website +import-keys-and-cert=Importar chave e certificado +upload-keys=Carregar chaves +download-keys-and-cert=Download chave e certificado +no-value-assigned.placeholder=Nenhum valor associado +remove=Remover +no-group-members=Nenhum membro +temporary=Temporária +join=Participar +event-type=Tipo de evento +events-config=Configurar eventos +event-listeners=Listeners de eventos +login-events-settings=Configuração de eventos de login +clear-events=Limpar eventos +saved-types=Tipos salvos +clear-admin-events=Limpar eventos administrativos +clear-changes=Cancelar mudanças +error=Erro + +# Authz +# Authz Common +authz-authorization=Autorização +authz-owner=Proprietário +authz-uri=URI +authz-scopes=Escopos +authz-resource=Recurso +authz-resource-type=Tipo de recurso +authz-resources=Recursos +authz-scope=Escopo +authz-authz-scopes=Autorização de escopos +authz-policies=Políticas +authz-permissions=Permissões +authz-evaluate=Avaliar +authz-icon-uri=URI do ícone +authz-select-scope=Selecione um escopo +authz-select-resource=Selecione um recurso +authz-associated-policies=Políticas associadas +authz-any-resource=Qualquer recurso +authz-any-scope=Qualquer escopo +authz-any-role=Qualquer role +authz-policy-evaluation=Avaliação da política +authz-select-client=Selecione um cliente +authz-select-user=Selecione um usuário +authz-entitlements=Direitos +authz-no-resources=Nenhum recurso +authz-result=Resultado +authz-authorization-services-enabled=Autorização habilitada +authz-required=Obrigatório + +# Authz Settings + +authz-policy-enforcement-mode=Modo de execução da política +authz-policy-enforcement-mode-enforcing=Restritiva +authz-policy-enforcement-mode-permissive=Permissiva +authz-policy-enforcement-mode-disabled=Desabilitada + +authz-remote-resource-management=Administração remota de recursos + +authz-export-settings=Exportar configurações + +# Authz Resource List +authz-no-resources-available=Nenhum recurso disponível. +authz-no-scopes-assigned=Nenhum escopo associado. +authz-no-type-defined=Nenhum tipo definido. +authz-no-permission-assigned=Nenhuma permissão associada. +authz-no-policy-assigned=Nenhuma política associada. +authz-create-permission=Criar permissão + +# Authz Resource Detail +authz-add-resource=Adicionar recurso + +# Authz Scope List +authz-add-scope=Adicionar escopo +authz-no-scopes-available=Nenhum escopo disponível. + +# Authz Scope Detail + +# Authz Policy List +authz-all-types=Todos os tipos +authz-create-policy=Criar política +authz-no-policies-available=Nenhuma política disponível + +# Authz Policy Detail +authz-policy-logic=Lógica +authz-policy-logic-positive=Positiva +authz-policy-logic-negative=Negativa +authz-policy-apply-policy=Aplicar política +authz-policy-decision-strategy=Estratégia de decisão +authz-policy-decision-strategy-affirmative=Afirmativa +authz-policy-decision-strategy-unanimous=Unânime +authz-policy-decision-strategy-consensus=Consensual +authz-select-a-policy=Selecionar uma política + +# Authz Role Policy Detail +authz-add-role-policy=Adicionar política de Role +authz-no-roles-assigned=Nenhum role associado + +# Authz User Policy Detail +authz-add-user-policy=Adicionar política de usuário +authz-no-users-assigned=Nenhum usuário associado + +# Authz Time Policy Detail +authz-add-time-policy=Adicionar política de tempo +authz-policy-time-not-on-after=Não em ou depois + +# Authz Drools Policy Detail +authz-add-drools-policy=Adicionar política Drools +authz-policy-drools-maven-artifact-resolve=Resolver +authz-policy-drools-maven-artifact=Artefato maven de política +authz-policy-drools-module=Módulo +authz-policy-drools-session=Sessão +authz-policy-drools-update-period=Atualizar período + +# Authz JS Policy Detail +authz-add-js-policy=Adicionar política Javascript +authz-policy-js-code=Código + + +# Authz Aggregated Policy Detail +authz-aggregated=Agregado +authz-add-aggregated-policy=Adicionar política agregada + +# Authz Permission List +authz-no-permissions-available=Nenhuma permissão disponível + +# Authz Permission Detail + +# Authz Resource Permission Detail +authz-add-resource-permission=Adicionar permissão para recurso +authz-permission-resource-apply-to-resource-type=Aplicar ao tipo de recurso + +# Authz Scope Permission Detail +authz-add-scope-permission=Adicionar permissão de escopo + +# Authz Evaluation +authz-evaluation-identity-information=Informação de identidade +authz-evaluation-new=Nova avaliação +authz-evaluation-re-evaluate=Re-avaliar +authz-evaluation-previous=Avaliação anterior +authz-evaluation-contextual-info=Informação contextual +authz-evaluation-contextual-attributes=Atributos contextuais +authz-evaluation-evaluate=Avaliar +authz-evaluation-any-resource-with-scopes=Qualquer recurso com escopo(s) +authz-evaluation-no-result=Não foi possível obter nenhum resultado para o pedido de autorização provida. Verifique os recurso(s) ou escopo(s) providos estão associados com alguma política. +authz-evaluation-no-policies-resource=Nenhma política foi encontrada para este recurso. +authz-evaluation-authorization-data=Resposta +authz-show-authorization-data=Exibir dados da autorização + +usermodel.prop.tooltip=Nome do método da propriedade na interface UserModel. Por exemplo, o valor 'email' iria referenciar o método UserModel.getEmail() . +usermodel.attr.tooltip=Nome do atributo do usuário que é uma chave de atributo no mapa UserModel.attribute. +userSession.modelNote.tooltip=Nome da nota de sessão do usuário salva no mapa UserSessionModel.note. +multivalued.tooltip=Indica se um atributo suporta múltiplos valores. Se verdadeiro, então a lista de todos os valores desse atributo será definida como o claim. Se falso, então apenas o primeiro valor será utilizado. +selectRole.tooltip=Entre com o role na caixa à esquerda, ou clique neste botão para navegar e selecionar o role desejado. +tokenClaimName.tooltip=Nome do claim para inserir no token. Pode ser um nome completo (fully qualified) como 'address.street'. Neste caso, um objeto json aninhado será criado. +jsonType.tooltip=Tipo JSON que deve ser utilizado para popular o claim json no token. Os valores válidos são Long, int boolean e String. +includeInIdToken.tooltip=O claim deve ser adicionado ao token de ID? +includeInAccessToken.tooltip=O claim deve ser adicionado ao token de acesso? +includeInUserInfo.tooltip=O claim deve ser adicionado à informação do usuário? +usermodel.clientRoleMapping.clientId.tooltip=ID do cliente para mapeamentos de roles +usermodel.clientRoleMapping.rolePrefix.tooltip=Um prefixo para cada role do cliente (opcional) +usermodel.realmRoleMapping.rolePrefix.tooltip=Um prefixo para cada Realm Role (opcional). +clients.tooltip=Os clientes são aplicativos de browser e serviços web confiáveis em um realm. Esses clientes podem solicitar login. Você também pode definir roles específicos do cliente. +authz-policy-role-clients.tooltip= Selecione um cliente a fim de filtrar os roles de cliente que podem ser aplicados a esta política. +direct-access-grants-enabled.tooltip=Habilita o suporte para concessões de acesso direto (Direct Access Grants), o que significa que o cliente tem acesso ao nome de usuário/senha e negocia diretamente com o servidor Keycloak pelo token de acesso. Em termos de especificações OAuth2, habilita suporte de "Resource Owner Password Credentials Grant" para este cliente. +service-accounts-enabled.tooltip=Permite autenticar este cliente no Keycloak e recuperar tokens de acesso dedicados para este cliente. Em termos da especificações OAuth2, habilita suporte para 'Client Credentials Grants' para este cliente. +include-authnstatement.tooltip=Deve ser adicionado um statement especificando o método e timestamp nas respostas de login? +sign-documents.tooltip=Devem os documentos SAML serem assinados pelo realm? +sign-assertions.tooltip=Devem as asserções dentro dos documentos SAML serem assinadas? Esta configuração não é necessária se o documento já está sendo assinado. +signature-algorithm.tooltip=O algoritmo de assinatura a ser utilizado para assinar documentos. +canonicalization-method.tooltip=Canonicalization Method para assinaturas XML. +encrypt-assertions.tooltip=Devem as asserções SAML serem encriptadas com a chave pública do cliente usando AES? +client-signature-required.tooltip=O cliente irá assinar os pedidos e respostas saml? E eles devem ser validados? +force-post-binding.tooltip=Sempre utilizar POST para respostas. +front-channel-logout.tooltip=Quando marcado, o logout requer um redirecionamento do browser para o cliente. Caso contrário o servidor executo uma invocação em background para o logout. +force-name-id-format.tooltip=Ignora o NameID de assunto solicitado e utiliza o configurado no console de administração. +name-id-format.tooltip=O formato de Name ID para utilizar como assunto. +root-url.tooltip=URL raiz adicionada à URLs relativas +valid-redirect-uris.tooltip=Padrão de URI válido para onde um navegador pode redirecionar depois de um login bem-sucedido ou sair. Wildcards simples são permitidos, por exemplo 'http://example.com/*'. Caminhos relativos podem ser especificados também, ex: /my/relative/path/*. Caminhos relativos são relativos à URL raiz do cliente, ou se nenhum for especificado a URL raiz do servidor é usado. Para SAML, é necessário definir padrões de URI válidos se você está contando com a URL do serviço consumidor incorporada com a solicitação de login. +base-url.tooltip=URL padrão para utilizar quando o servidor de autenticação necessita redirecionar ou linkar para o cliente. +admin-url.tooltip=URL para a inteface administrativa do cliente. Defina este valor se o cliente suporta a API do adaptador REST. Esta API rest permite que o servidor de autenticação envie políticas de revogação e outras tarefas administrativas. Geralmente este valor é definido apontando para a URL base do cliente. +master-saml-processing-url.tooltip=Se configurado, esta URL será utilizada para todos os bindings do "SP's Assertion Consumer" e "Single Logout Services". Ela pode ser sobreescriva idnvidualmente para cada ligação e serviço na Configuração Detalhada do Endpoint SAML. +idp-sso-url-ref.tooltip=Nome do fragmento URL para referenciar o cliente quando você deseja um SSO iniciado por IDP. Deixar este campo vazio irá desabilitar SSO iniciado por IDP. A URL que você irá referenciar do seu browser será: {server-root}/realms/{realm}/protocol/saml/clients/{client-url-name} +idp-sso-relay-state.tooltip=O estado de Relay que você deseja enviar com um pedido SAML quando você deseja realizar SSO iniciado por IDP. +web-origins.tooltip=Permitir origens CORS. Para permitir todas as URIs de redirecionamento de origem válidas adicionar '+'. Para permitir todas as origens adicionar '*'. +fine-saml-endpoint-conf.tooltip=Expanda esta sessão para configurar URLs específics para 'Assertion Consumer' e 'Single Logout Service'. +assertion-consumer-post-binding-url.tooltip=URL de ligação SAML via post para as asserções de consumidor de serviços do cliente (respostas de login). Você pode deixar este campo em branco se você não tiver uma URL para esta ligação. +assertion-consumer-redirect-binding-url.tooltip=URL de ligação SAML de redirecionamento para as asserções de consumidor de serviços do cliente (respostas de login). Você pode deixar este campo em branco se você não tiver uma URL para esta ligação. +logout-service-binding-post-url.tooltip=URL de ligação SAML via post para o serviço de logout único do cliente. Voce pode deixar este campo em branco se estiver usando uma ligação diferente. +logout-service-redir-binding-url.tooltip=URL de ligação SAML de redirecionamento para o serviço de logout único do cliente. Voce pode deixar este campo em branco s e estiver usando uma ligação diferente. +mappers.tooltip=Mapeamentos de protocolo executam transformações em tokens e documentos. Eles podem realizar coisas como mapear dados de usuários para claims de protocolo, ou apenas transformar qualquer solicitação entre o cliente e o servidor de autenticação. +scope.tooltip=Escopos de mapeamento permitem que você restrinja quais mapeamentos de roles de usuário são inclusos nos tokens de acesso solicitado pelo cliente. +ldap.search-scope.tooltip=Para um nível nós pesquisamos somente os usuários nos DNs especificados pelo campo User DNs. Para subtree, nós pesquisamos na sub-árvore completa. Verifique a documentação do LDAP para mais detalhes. +authz-permission-scope-scope.tooltip=Define que esta permissões deve ser aplicada para um ou mais escopos. +sessions.tooltip=Exibir as sessões ativas para este cliente. Permite que você veja quais usuários estão ativos e quando eles logaram. +active-sessions.tooltip=Total de sessões de usuário ativas para este cliente. +show-sessions.tooltip=Atenção, esta é uma operação potencialmente cara dependendo do número de sessões ativas. +offline-access.tooltip=Exibe as sessões offline para este cliente. Permite que você veja quantos usuários obtém tokens offline e quando eles os obtiveram. PAra revogar todos os tokens do cliente, vá para a aba Revogações e defina o valor do campo 'não antes de' para 'agora'. +installation.tooltip=Ferramenta de auxílio para gerar vários formatos de adaptadores de cliente que você poderá fazer download depois ou copiar e colar para configurar seus cliente. +service-account-roles.tooltip=Permite que você autentique mapeamentos de roles para as contas de serviço dedicadas à este cliente. +client-authenticator.tooltip=Autenticador de Cliente usado para autenticar este cliente ao servidor Keycloak +certificate.tooltip=Certificado do cliente para validar JWT emitidos pelo cliente e assinados pela chave privada do cliente da sua keystore. +validating-x509-certificate.tooltip=O certificado em formato PEM que deve ser usado para verificar assinaturas. +archive-format.tooltip=Keystore Java ou arquivo em formato PKCS12. +key-alias.tooltip=Alias do arquivo para sua chave privada e certificado. +key-password.tooltip=Senha para acessar a chave privada no certificado. +store-password.tooltip=Senha para acessar o arquivo em si. +jwt-import.key-alias.tooltip=Alias do arquivo para o seu certificado. +registrationAccessToken.tooltip=O token de acesso para registro provê acesso aos cliente para o serviço de registro cliente. +scope-param-required.tooltip=Este role somente será concedido se os parâmetros de escopo com os nomes dos roles forem utilizados durante a autorização/solicitação de token. +composite-roles.tooltip=Quando este role é associado/removido de um usuário, qualquer role associado com ele também será adicionado/removido implicitamente. +composite.associated-realm-roles.tooltip=Roles de nível de realm associados com este role composto. +composite.available-realm-roles.tooltip=Roles de nível de realm disponíveis para este role composto. +available-roles.tooltip=Roles para este cliente que você pode associar a este role composto. +scope.available-roles.tooltip=Roles do Realm que podem ser associados a este escopo. +service-account.available-roles.tooltip=Roles do Realm que podem ser associados a contas de serviço. +client.associated-roles.tooltip=Roles do Cliente associados a este role composto. +full-scope-allowed.tooltip=Permite a você desabilitar todas as restrições. +assigned-roles.tooltip=Roles do Realm associados ao escopo. +service-account.assigned-roles.tooltip=Roles do Realm associados a conta de serviço. +group.assigned-roles.tooltip=Roles do Realm mapeados ao grupo. +realm.effective-roles.tooltip=Roles do Realm associados que podem ter sido herdados de um role composto. +select-client-roles.tooltip=Selecione o cliente para visualizar os roles de cliente. +assign.available-roles.tooltip=Roles de Cliente disponíveis para associação. +client.assigned-roles.tooltip=Roles de Cliente associados. +client.effective-roles.tooltip=Roles de cliente associados que podem ter sido herdados de um role composto. +node-reregistration-timeout.tooltip=Intervalo para especificar o tempo máximo para nós clientes de cluster registrados se re-registrarem. Se os nós do cluster não enviarem solicitações de re-registro dentro deste intervalo eles serão deregistrados do Keycloak. +client-revoke.not-before.tooltip=Revocar qualquer token emitido antes desta data para este cliente. +client-revoke.push.tooltip=Se a URL de administração estiver configurada para este cliente, envie esta política para este cliente. +offline-tokens.tooltip=Número total de tokens offline para este cliente. +show-offline-tokens.tooltip=Atenção, esta é uma operação potencialmente cara dependendo do número de tokens offline. +realm-certificate-alias.tooltip=O certificado do Realm também é guardado em arquivo. Este é o alias para ele. +saml-encryption-key.tooltip=Chave de encriptação SAML. +mapper.consent-required.tooltip=Ao conceder acesso temporário, deve o usuário consentir em prover esta informação para o cliente? +consent-text.tooltip=Texto para exibir na página de consentimento +mapper-type.tooltip=Tipo do mapeamento +redirect-uri.tooltip=A url de redirecionamento para usar quando da configuração do provedor de identidade. +identity-provider.alias.tooltip=O alias é o identificador único de um provedor de identidade e também é utilizado para construir a uri de redirecionamento. +identity-provider.enabled.tooltip=Habilita/Desabilita este provedor de identidade. +identity-provider.authenticate-by-default.tooltip=Indica se este provedor deve ser tentado por padrão para a autenticação mesmo ante de exibir a tela de login. +identity-provider.store-tokens.tooltip=Habilita/desabilita se os tokens deve ser guardados depois de autenticar os usuários. +identity-provider.stored-tokens-readable.tooltip=Habilita/desabilita se novos usuários podem ler quaisquer tokens salvo. Isto irá adicionar o role broker.read-token. +update-profile-on-first-login.tooltip=Define condições onde um usuário precisa atualizar o seu perfil durante o primeiro login. +trust-email.tooltip=Se habilitado então o e-mail provido por este provedor não será verificado mesmo que a verificação esteja habilitada para este realm. +first-broker-login-flow.tooltip=Alias do fluxo de autenticação que será invocado depois do primeiro login com este provedor de identificação. O termo 'Primeiro Login' significa que ainda não existe uma conta no Keycloak ligada a esta conta autenticada neste provedor. +post-broker-login-flow.tooltip=Alias do fluxo de autenticação que será invocado depois de cada login com esse provedor de identificação. É útil se você pretende adicionar verificações adicionais de cada usuário autenticado com este provedor (por exemplo OTP). Deixa vazio se você não deseja que nenhum autenticador adicionar seja invocado depois do login com este provedor de identificação. Note também que as implementações de autenticação devem assumir que o usuários já está definido na ClientSessioncom e com o provedor de identidade já definido. +openid-connect-config.tooltip=OIDC SP e configuração externa IDP. +authorization-url.tooltip=A URL de autorização. +token-url.tooltip=A URL do Token. +identity-provider.logout-url.tooltip='End session endpoint' para utilizar para realizar logour dos usuários do IDP externo. +backchannel-logout.tooltip=O IDP externo suporta logou via backchannel? +user-info-url.tooltip=A Url de informações de usuário. Opcional. +identity-provider.client-id.tooltip=O cliente ou identificador do cliente registrado junto ao provedor de identificação. +client-secret.tooltip=O cliente ou senha do cliente registrado junto ao provedor de identificação. +social.client-secret.tooltip=A senha do cliente registrado junto ao provedor de identificação. +issuer.tooltip=O identificador de emissor para o emissor da resposta. Se não for provido nenhuma validação será realizada. +identity-provider.default-scopes.tooltip=Os escopos que serão enviados ao solicitar autorização. Pode ser uma lista de escopos separadas por espaço. Valor padrão é 'openid'. +prompt.tooltip=Especifica se o Servidor de Autorização solicita ao Usuário Final reautenticação e consentimento. +identity-provider.validate-signatures.tooltip=Habilita/Desabilita a validação de assinatura de IDP externo. +identity-provider.validating-public-key.tooltip=A chave pública em formato PEM que deve ser usada para verificar assinaturas de IDP externos. +import-external-idp-config.tooltip=Permite que vocÊ carregue metadata de IDP externos de um arquivo de configuração ou baixando a partir de uma URL. +identity-provider.import-from-url.tooltip=Importar metadata de um descritor de descoberta remoto do IDP. +identity-provider.import-from-file.tooltip=Importar metadata fr um descritor de descoberta baixado do IDP. +identity-provider.saml-config.tooltip=SAML SP e configuração de IDP externo. +saml.single-signon-service-url.tooltip=A Url que deve ser utilizada para enviar solicitações de autenticação (SAML AuthnRequest). +saml.single-logout-service-url.tooltip=A Url que deve ser utilizada para enviar solicitações de logout. +nameid-policy-format.tooltip=Especifica a referência de URI correspondente a um formato de nome identificador. O padrão é urn:oasis:names:tc:SAML:2.0:nameid-format:persistent. +http-post-binding-response.tooltip=Indica se deve se responder a solicitações utilizando HTTP-POST. Se falso, HTTP-REDIRECT será utilizado. +http-post-binding-for-authn-request.tooltip=Indica se o AuthnRequest deve ser enviado utilizando HTTP-POST. Se falso, HTTP-REDIRECT será utilizado. +want-authn-requests-signed.tooltip=Indicate se um provedor de identificação deve experar um AuthnRequest assinado. +identity-provider.force-authentication.tooltip=Indica se um provedor de identificação deve autenticar o apresentador diretamente ao invés de confiar em um contexto de segurança anterior. +saml.validate-signature.tooltip=Habilita/Desabilita validação de assinaturas de respostas SAML. +saml.import-from-url.tooltip=Importar metadata de um descritor de entidade IDP SAML remoto. +social.client-id.tooltip=O identificador do cliente registrado com o provedor de identificação. +social.default-scopes.tooltip=Os escopos que serão enviados ao solicitar autorização. Veja a documentação para valores possíveis, separador e valores padrão. +stackoverflow.key.tooltip=A chave de cliente obtida do registro no Stack Overflow. +client-templates.tooltip=Modelos de cliente permitem que você defina configurações comuns que serão compartilhadas entre múltiplos clientes. +group.add-selected.tooltip=Roles do Realm que serão associadas ao grupo. +group.effective-roles.tooltip=Todos os mapeamentos de roles do Realm. Alguns roles exibidos podem ter sido herdados de um role composto mapeado. +group.available-roles.tooltip=Roles associáveis deste cliente. +group.assigned-roles-client.tooltip=Mapeamentos de roles para este cliente. +group.effective-roles-client.tooltip=Mapeamentos de roles para este cliente. Alguns roles exibidos podem ter sido herdados de um role composto mapeado. +user.add-selected.tooltip=Roles do Realm que podem ser associados ao usuário. +user.assigned-roles.tooltip=Roles do Realm mapeados para o usuário. +user.effective-roles.tooltip=Todos os mapeamentos de roles do Realm. Alguns roles exibidos podem ter sido herdados de um role composto mapeado. +user.available-roles.tooltip=Roles associáveis deste cliente. +user.assigned-roles-client.tooltip=Mapeamentos de roles para este cliente. +user.effective-roles-client.tooltip=Mapeamentos de Role para este cliente. Alguns roles exibidos podem ter sido herdados de um role composto mapeado. +default.available-roles.tooltip=Roles do nível de Realm que podem ser associados. +realm-default-roles.tooltip=Roles do nível de Realm associados a novos usuários. +default.available-roles-client.tooltip=Roles para este cliente que são associáveis por padrão. +client-default-roles.tooltip=Roles para este cliente que são associados como roles padrão. +composite.available-roles.tooltip=Roles do nível de Realm associáveis com este role composto. +composite.associated-roles.tooltip=Roles do nível de Realm associados com este role composto. +composite.available-roles-client.tooltip=Roles para este cliente que podem ser associados com este role composto. +composite.associated-roles-client.tooltip=Roles do cliente associados com este role composto. +partial-import.tooltip=Importação parcial permite que você importe usuários, clientes, e outros recursos de um arquivo json previamente exportado. +if-resource-exists.tooltip=Especifica o que deve ser feito se você tentar importar um recurso já existente. +realm-roles.tooltip=Roles do Realm que podem ser selecionados. +authz-policy-role-realm-roles.tooltip=Especifica quais role(s) de *realm* são permitidos por esta política. +client-roles.tooltip=Roles do cliente que podem ser selecionados. +authz-policy-role-client-roles.tooltip=Especifica quais role(s) do *cliente* são permitidos por esta política. +client-template.tooltip=Modelo de cliente do qual ete cliente herda as configurações. +client-template.name.tooltip=Nome do modelo de cliente. Deve ser único neste Realm. +client-template.description.tooltip=Descrição do modelo de cliente. +client-template.protocol.tooltip=Qual configuração de protocolo SSO será provida por este modelo de cliente. +console-display-name.tooltip=Nome de exibição do provedor quando linkado no console de administração. +priority.tooltip=Prioridade do provedor quando da busca de usuários. Valores mais baixos são utilizados primeiro. +periodic-full-sync.tooltip=Habilitar ou não a sincronização completa periódica dos usuários deste provedor. +ldap.periodic-full-sync.tooltip=Habilitar ou não a sincronização completa dos usuários do LDAP para o Keycloak. +full-sync-period.tooltip=Intervalo para a sincronização completa em segundos. +periodic-changed-users-sync.tooltip=Habilitar ou não a sincronização de usuários novos ou alterados do provedor para o Keycloak. +ldap.periodic-changed-users-sync.tooltip=Habilitar ou não a sincronização de usuários novos ou alterados do LDAP para o Keycloak. +changed-users-sync-period.tooltip=Intervalo para sincronização dos usuários alterados ou novos do provedor em segundos. +ldap.changed-users-sync-period.tooltip=Intervalo para sincronização dos usuários alterados ou novos do LDAP em segundos. +kerberos-realm.tooltip=Nome do realm kerberos. Por exemplo FOO.ORG +server-principal.tooltip=Nome completo do principal do servidor para o serviço HTTP incluindo o servidor e nome do domínio. Por exemplo HTTP/host.foo.org@FOO.ORG +keytab.tooltip=Localização do arquivo KeyTab do Kerberos contendo as credenciais do principal do servidor. Por exemplo /etc/krb5.keytab +debug.tooltip=Habilita/Desabilita log de nível debug para a saída padrão para Krb5LoginModule. +allow-password-authentication.tooltip=Habilita/Desabilita a possibilidade de autenticação via usuário/senha contra o banco Kerberos +edit-mode.tooltip=READ_ONLY significa que atualizações de senhas não são permitidas e o usuário sempre autenticará com a senha do Kerberos. UNSYNCED significa que o usuário pode alterar a senha no banco do Keycloak e essa senha será utilizda ao invés da senha do Kerberos. +ldap.edit-mode.tooltip=READ_ONLY é um LDAP somente leitura. WRITABLE significa que os dados serão sicronizados de volta para o LDAP on demand. UNSYNCED significa que os dados do usuário serão importados, mas não sicronizados de volta para o LDAP. +update-profile-first-login.tooltip=Atualizar o perfil no primeiro login +ldap.sync-registrations.tooltip=Os novos usuários criados devem ser criados no LDAP? A prioridade afeta qual provedor é utilizado para sincronizar o novo usuário. +ldap.vendor.tooltip=LDAP vendor (provedor) +username-ldap-attribute.tooltip=Nome do atributo do LDAP que será mapeado como nome do usuário no Keycloak. Para muitos servidores LDAP este valor deve ser 'uid'. Para o Active Directory pode ser 'sAMAccountName' ou 'cn'. O atributo deve ser preenchido para todos os registros de usuários do LDAP que você deseja importar do LDAP para o Keycloak. +rdn-ldap-attribute.tooltip=Nome do atributo LDAP que é utilizado como RDN (atributo topo) do DN do usuário típico. Geralmente é o mesmo que o atributo do nome do usuário, mas isto não é obrigatório. Por exemplo para o Active Directory é comum utilizar 'cn' como atributo RDN quando o atributo do nome de usuário pode ser 'sAMAccountName', +uuid-ldap-attribute.tooltip=Nome do atributo LDAP que é utilizado como identificador único do objeto(UUID) para objetos no LDAP. Para muitos servidores LDAP o valor é 'entryUUID', porém alguns são diferentes. Por exemplo para o Active Directory este valor para objetos no LDAP deve ser 'objectGUID'. Se o seu servidor LDAP realmente não suporta a noção de UUID, vocÊ pode usar qualquer outro atributo que seja único na árvore de usuários do LDAP. Por exemplo 'uid' ou 'entryDN'. +ldap.user-object-classes.tooltip=Todos os valores de objectClass para usuários no LDAP separados por vírgula. Por exemplo: 'inetOrgPerson, organizationalPerson'. Usuários criados no Keycloak serão enviados para o LDAP com todas essas classes de objetos associadas e dados de usuários do LDAP somente serão localizados se ele contiverem todas estas classes de objeto. +ldap.connection-url.tooltip=Conexão URL para o seu servidor LDAP +ldap.users-dn.tooltip=DN completo da árvore LDAP onde os usuários estão. Este DN é o pai dos usuários do LDAP. Por exemplo pode ser 'ou=users,dc=example,dc=com' entendendo que o usuário típico irá ter um DN como 'uid=john,ou=users,dc=example,dc=com'. +ldap.authentication-type.tooltip=Tipo de autenticação no LDAP. No momento apenas os mecanismos 'none' (anonymous LDAP authentication) ou 'simple' (Credencial de bind + senha para bind) estão disponíveis. +ldap.bind-dn.tooltip=DN do administrador do LDAP, que será utilizado pelo Keycloak para acessar o servidor LDAP. +ldap.bind-credential.tooltip=Senha do administrador do LDAP +ldap.custom-user-ldap-filter.tooltip=Additional LDAP Filter for filtering searched users. Leave this empty if you don't need additional filter. Make sure that it starts with '(' and ends with ')' +ldap.use-truststore-spi.tooltip=Specifies whether LDAP connection will use the truststore SPI with the truststore configured in keycloak-server.json. 'Always' means that it will always use it. 'Never' means that it won't use it. 'Only for ldaps' means that it will use if your connection URL use ldaps. Note even if keycloak-server.json is not configured, the default Java cacerts or certificate specified by 'javax.net.ssl.trustStore' property will be used. +ldap.connection-pooling.tooltip=Does Keycloak should use connection pooling for accessing LDAP server +ldap.pagination.tooltip=Does the LDAP server support pagination. +ldap.allow-kerberos-authentication.tooltip=Enable/disable HTTP authentication of users with SPNEGO/Kerberos tokens. The data about authenticated users will be provisioned from this LDAP server +ldap.use-kerberos-for-password-authentication.tooltip=Use Kerberos login module for authenticate username/password against Kerberos server instead of authenticating against LDAP server with Directory Service API +ldap.batch-size.tooltip=Count of LDAP users to be imported from LDAP to Keycloak within single transaction. +identity-provider-user-id.tooltip=Unique ID of the user on the Identity Provider side +identity-provider-username.tooltip=Username on the Identity Provider side +browser-flow.tooltip=Select the flow you want to use for browser authentication. +registration-flow.tooltip=Select the flow you want to use for registration. +direct-grant-flow.tooltip=Select the flow you want to use for direct grant authentication. +reset-credentials.tooltip=Select the flow you want to use when the user has forgotten their credentials. +client-authentication.tooltip=Select the flow you want to use for authentication of clients. +authenticator.alias.tooltip=Name of the configuration +otp-type.tooltip=totp is Time-Based One Time Password. 'hotp' is a counter base one time password in which the server keeps a counter to hash against. +otp-hash-algorithm.tooltip=What hashing algorithm should be used to generate the OTP. +otp.number-of-digits.tooltip=How many digits should the OTP have? +otp.look-ahead-window.tooltip=How far ahead should the server look just in case the token generator and server are out of time sync or counter sync? +otp.initial-counter.tooltip=What should the initial counter value be? +otp-token-period.tooltip=How many seconds should an OTP token be valid? Defaults to 30 seconds. +admin-events.tooltip=Displays saved admin events for the realm. Events are related to admin account, for example a realm creation. To enable persisted events go to config. +clear-admin-events.tooltip=Deletes all admin events in the database. +resource-path.tooltip=Filter by resource path. Supports wildcards '*' to match a single part of the path and '**' matches multiple parts. For example 'realms/*/clients/asbc' matches client with id asbc in any realm, while or 'realms/master/**' matches anything in the master realm. +auth.default-action.tooltip=If enabled, any new user will have this required action assigned to it. +groups.default-groups.tooltip=Set of groups that new users will automatically join. +flow.alias.tooltip=Specifies display name for the flow. +top-level-flow-type.tooltip=What kind of top level flow is it? Type 'client' is used for authentication of clients (applications) when generic is for users and everything else +flow-type.tooltip=What kind of form is it +default-groups.tooltip=Newly created or registered users will automatically be added to these groups +available-groups.tooltip=Select a group you want to add as a default. +membership.available-groups.tooltip=Groups a user can join. Select a group and click the join button. +events.tooltip=Displays saved events for the realm. Events are related to user accounts, for example a user login. To enable persisted events go to config. +login.save-events.tooltip=If enabled login events are saved to the database which makes events available to the admin and account management consoles. +admin.save-events.tooltip=If enabled admin events are saved to the database which makes events available to the admin console. +events-config.tooltip=Displays configuration options to enable persistence of user and admin events. +event-listeners.tooltip=Configure what listeners receive events for the realm. +clear-events.tooltip=Deletes all events in the database. +events.expiration.tooltip=Sets the expiration for events. Expired events are periodically deleted from the database. +saved-types.tooltip=Configure what event types are saved. +include-representation.tooltip=Include JSON representation for create and update requests. +credentials.temporary.tooltip=If enabled user is required to change password on next login +credentials.remove-totp.tooltip=Remove one time password generator for user. +credentials.reset-actions.tooltip=Set of actions to execute when sending the user a Reset Actions Email. 'Verify email' sends an email to the user to verify their email address. 'Update profile' requires user to enter in new personal information. 'Update password' requires user to enter in a new password. 'Configure TOTP' requires setup of a mobile password generator. +credentials.reset-actions-email.tooltip=Sends an email to user with an embedded link. Clicking on link will allow the user to execute the reset actions. They will not have to login prior to this. For example, set the action to update password, click this button, and the user will be able to change their password without logging in. +user-enabled.tooltip=A disabled user cannot login. +user-temporarily-locked.tooltip=The user may have been locked due to failing to login too many times. +email-verified.tooltip=Has the user's email been verified? +required-user-actions.tooltip=Require an action when the user logs in. 'Verify email' sends an email to the user to verify their email address. 'Update profile' requires user to enter in new personal information. 'Update password' requires user to enter in a new password. 'Configure TOTP' requires setup of a mobile password generator. +impersonate-user.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. +group-membership.tooltip=Groups user is a member of. Select a listed group and click the Leave button to leave the group. +import-keys-and-cert.tooltip=Upload the client's key pair and cert. +authz-icon-uri.tooltip=An URI pointing to an icon. +authz-authorization-services-enabled.tooltip=Enable/Disable fine-grained authorization support for a client +authz-import-config.tooltip=Import a JSON file containing authorization settings for this resource server. +authz-policy-enforcement-mode.tooltip=The policy enforcement mode dictates how policies are enforced when evaluating authorization requests. 'Enforcing' means requests are denied by default even when there is no policy associated with a given resource. 'Permissive' means requests are allowed even when there is no policy associated with a given resource. 'Disabled' completely disables the evaluation of policies and allow access to any resource. +authz-remote-resource-management.tooltip=Should resources be managed remotely by the resource server? If false, resources can only be managed from this admin console. +authz-export-settings.tooltip=Export and download all authorization settings for this resource server. +authz-resource-name.tooltip=An unique name for this resource. The name can be used to uniquely identify a resource, useful when querying for a specific resource. +authz-resource-owner.tooltip=The owner of this resource. +authz-resource-type.tooltip=The type of this resource. It can be used to group different resource instances with the same type. +authz-resource-uri.tooltip=An URI that can also be used to uniquely identify this resource. +authz-resource-scopes.tooltip=The scopes associated with this resource. +authz-scope-name.tooltip=An unique name for this scope. The name can be used to uniquely identify a scope, useful when querying for a specific scope. +authz-policy-name.tooltip=The name of this policy. +authz-policy-description.tooltip=A description for this policy. +authz-policy-logic.tooltip=The logic dictates how the policy decision should be made. If 'Positive', the resulting effect (permit or deny) obtained during the evaluation of this policy will be used to perform a decision. If 'Negative', the resulting effect will be negated, in other words, a permit becomes a deny and vice-versa. +authz-policy-apply-policy.tooltip=Specifies all the policies that must be applied to the scopes defined by this policy or permission. +authz-policy-decision-strategy.tooltip=The decision strategy dictates how the policies associated with a given policy are evaluated and how a final decision is obtained. 'Affirmative' means that at least one policy must evaluate to a positive decision in order to the overall decision be also positive. 'Unanimous' means that all policies must evaluate to a positive decision in order to the overall decision be also positive. 'Consensus' means that the number of positive decisions must be greater than the number of negative decisions. If the number of positive and negative is the same, the final decision will be negative. +authz-policy-user-users.tooltip=Specifies which user(s) are allowed by this policy. +authz-policy-time-not-before.tooltip=Defines the time before which the policy MUST NOT be granted. Only granted if current date/time is after or equal to this value. +authz-policy-time-not-on-after.tooltip=Defines the time after which the policy MUST NOT be granted. Only granted if current date/time is before or equal to this value. +authz-policy-drools-maven-artifact.tooltip=A Maven GAV pointing to an artifact from where the rules would be loaded from. Once you have provided the GAV, you can click *Resolve* to load both *Module* and *Session* fields. +authz-policy-drools-module.tooltip=The module used by this policy. You must provide a module in order to select a specific session from where rules will be loaded from. +authz-policy-drools-session.tooltip=The session used by this policy. The session provides all the rules to evaluate when processing the policy. +authz-policy-drools-update-period.tooltip=Specifies an interval for scanning for artifact updates. +authz-policy-js-code.tooltip=The JavaScript code providing the conditions for this policy. +authz-permission-name.tooltip=The name of this permission. +authz-permission-description.tooltip=A description for this permission. +authz-permission-resource-apply-to-resource-type.tooltip=Specifies if this permission would be applied to all resources with a given type. In this case, this permission will be evaluated for all instances of a given resource type. +authz-permission-resource-resource.tooltip=Specifies that this permission must be applied to a specific resource instance. +authz-permission-resource-type.tooltip=Specifies that this permission must be applied to all resources instances of a given type. +authz-permission-scope-resource.tooltip=Restrict the scopes to those associated with the selected resource. If not selected all scopes would be available. +authz-evaluation-identity-information.tooltip=The available options to configure the identity information that will be used when evaluating policies. +authz-evaluation-client.tooltip=Select the client making this authorization request. If not provided, authorization requests would be done based on the client you are in. +authz-evaluation-user.tooltip=Select an user whose identity is going to be used to query permissions from the server. +authz-evaluation-role.tooltip=Select the roles you want to associate with the selected user. +authz-evaluation-contextual-info.tooltip=The available options to configure any contextual information that will be used when evaluating policies. +authz-evaluation-contextual-attributes.tooltip=Any attribute provided by a running environment or execution context. +authz-evaluation-permissions.tooltip=The available options to configure the permissions to which policies will be applied. +authz-evaluation-result.tooltip=The overall result for this permission request. +authz-evaluation-scopes.tooltip=The list of allowed scopes. +authz-evaluation-policies.tooltip=Details about which policies were evaluated and their decisions. +authz-evaluation-authorization-data.tooltip=Represents a token carrying authorization data as a result of the processing of an authorization request. This representation is basically what Keycloak issues to clients asking for permissions. Check the 'authorization' claim for the permissions that were granted based on the current authorization request. diff --git a/themes/src/main/resources/theme/base/admin/messages/admin-messages_ru.properties b/themes/src/main/resources/theme/base/admin/messages/admin-messages_ru.properties index c28da5677f..6a59cc9f36 100644 --- a/themes/src/main/resources/theme/base/admin/messages/admin-messages_ru.properties +++ b/themes/src/main/resources/theme/base/admin/messages/admin-messages_ru.properties @@ -233,8 +233,8 @@ assertion-consumer-post-binding-url=\u0421\u0432\u044F\u0437\u044B\u0432\u0430\u assertion-consumer-post-binding-url.tooltip=SAML POST \u0441\u0432\u044F\u0437\u0430\u043D\u043D\u044B\u0439 URL \u0434\u043B\u044F \u043A\u043B\u0438\u0435\u043D\u0442\u0441\u043A\u0438\u0445 \u0441\u0435\u0440\u0432\u0438\u0441\u043E\u0432 \u0443\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u043D\u043E\u0433\u043E \u043F\u043E\u0442\u0440\u0435\u0431\u0438\u0442\u0435\u043B\u044F (\u0437\u0430\u043F\u0440\u043E\u0441\u044B \u0432\u0445\u043E\u0434\u0430). \u0412\u044B \u043C\u043E\u0436\u0435\u0442\u0435 \u043E\u0441\u0442\u0430\u0432\u0438\u0442\u044C \u044D\u0442\u043E \u043F\u043E\u043B\u0435 \u043F\u0443\u0441\u0442\u044B\u043C, \u0435\u0441\u043B\u0438 \u043D\u0435 \u0438\u043C\u0435\u0435\u0442\u0435 URL \u0434\u043B\u044F \u043E\u0441\u0443\u0449\u0435\u0441\u0442\u0432\u043B\u0435\u043D\u0438\u044F \u0442\u0430\u043A\u043E\u0439 \u0441\u0432\u044F\u0437\u043A\u0438. assertion-consumer-redirect-binding-url=\u0421\u0432\u044F\u0437\u044B\u0432\u0430\u043D\u0438\u0435 URL \u043F\u0435\u0440\u0435\u0430\u0434\u0440\u0435\u0441\u0430\u0446\u0438\u0438 \u0441 \u0441\u0435\u0440\u0432\u0438\u0441\u043E\u043C \u0443\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u043D\u043E\u0433\u043E \u043F\u043E\u0442\u0440\u0435\u0431\u0438\u0442\u0435\u043B\u044F assertion-consumer-redirect-binding-url.tooltip=SAML \u043F\u0435\u0440\u0435\u0430\u0434\u0440\u0435\u0441\u0430\u0446\u0438\u044F \u043D\u0430 \u0441\u0432\u044F\u0437\u0430\u043D\u043D\u044B\u0439 URL \u0434\u043B\u044F \u043A\u043B\u0438\u0435\u043D\u0442\u0441\u043A\u043E\u0433\u043E \u0441\u0435\u0440\u0432\u0438\u0441\u0430 \u0443\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u043D\u043E\u0433\u043E \u043F\u043E\u0442\u0440\u0435\u0431\u0438\u0442\u0435\u043B\u044F (\u0437\u0430\u043F\u0440\u043E\u0441\u044B \u0432\u0445\u043E\u0434\u0430). \u0412\u044B \u043C\u043E\u0436\u0435\u0442\u0435 \u043E\u0441\u0442\u0430\u0432\u0438\u0442\u044C \u044D\u0442\u043E \u043F\u043E\u043B\u0435 \u043F\u0443\u0441\u0442\u044B\u043C, \u0435\u0441\u043B\u0438 \u0432\u044B \u043D\u0435 \u0438\u043C\u0435\u0435\u0442\u0435 URL \u0434\u043B\u044F \u0442\u0430\u043A\u043E\u0433\u043E \u0441\u0432\u044F\u0437\u044B\u0432\u0430\u043D\u0438\u044F. -logout-service-binding-post-url=\u0421\u0432\u044F\u0437\u044B\u0432\u0430\u043D\u0438\u0435 URL \u0434\u043B\u044F \u0432\u044B\u0445\u043E\u0434\u0430 \u0438\u0437 \u0441\u0435\u0440\u0432\u0438\u0441\u0430 \u0434\u043B\u044F POST-\u043C\u0435\u0442\u043E\u0434\u0430 -logout-service-binding-post-url.tooltip=SAML POST \u0441\u0432\u044F\u0437\u0430\u043D\u043D\u044B\u0439 URL \u0434\u043B\u044F \u043A\u043B\u0438\u0435\u043D\u0442\u0441\u043A\u043E\u0433\u043E \u0441\u0435\u0440\u0432\u0438\u0441\u0430 \u0435\u0434\u0438\u043D\u043E\u0433\u043E \u0432\u044B\u0445\u043E\u0434\u0430. \u0412\u044B \u043C\u043E\u0436\u0435\u0442\u0435 \u043E\u0441\u0442\u0430\u0432\u0438\u0442\u044C \u044D\u0442\u043E \u043F\u043E\u043B\u0435 \u043F\u0443\u0441\u0442\u044B\u043C, \u0435\u0441\u043B\u0438 \u0432\u044B \u0438\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0435\u0442\u0435 \u0440\u0430\u0437\u043B\u0438\u0447\u043D\u044B\u0435 \u0441\u0432\u044F\u0437\u0438 +logout-service-post-binding-url=\u0421\u0432\u044F\u0437\u044B\u0432\u0430\u043D\u0438\u0435 URL \u0434\u043B\u044F \u0432\u044B\u0445\u043E\u0434\u0430 \u0438\u0437 \u0441\u0435\u0440\u0432\u0438\u0441\u0430 \u0434\u043B\u044F POST-\u043C\u0435\u0442\u043E\u0434\u0430 +logout-service-post-binding-url.tooltip=SAML POST \u0441\u0432\u044F\u0437\u0430\u043D\u043D\u044B\u0439 URL \u0434\u043B\u044F \u043A\u043B\u0438\u0435\u043D\u0442\u0441\u043A\u043E\u0433\u043E \u0441\u0435\u0440\u0432\u0438\u0441\u0430 \u0435\u0434\u0438\u043D\u043E\u0433\u043E \u0432\u044B\u0445\u043E\u0434\u0430. \u0412\u044B \u043C\u043E\u0436\u0435\u0442\u0435 \u043E\u0441\u0442\u0430\u0432\u0438\u0442\u044C \u044D\u0442\u043E \u043F\u043E\u043B\u0435 \u043F\u0443\u0441\u0442\u044B\u043C, \u0435\u0441\u043B\u0438 \u0432\u044B \u0438\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0435\u0442\u0435 \u0440\u0430\u0437\u043B\u0438\u0447\u043D\u044B\u0435 \u0441\u0432\u044F\u0437\u0438 logout-service-redir-binding-url=\u0421\u0432\u044F\u0437\u044B\u0432\u0430\u043D\u0438\u0435 URL \u043F\u0435\u0440\u0435\u0430\u0434\u0440\u0435\u0441\u0430\u0446\u0438\u0438 \u0434\u043B\u044F \u0432\u044B\u0445\u043E\u0434\u0430 \u0438\u0437 \u0441\u0435\u0440\u0432\u0438\u0441\u0430 logout-service-redir-binding-url.tooltip=SAML \u043F\u0435\u0440\u0435\u0430\u0434\u0440\u0435\u0441\u0443\u0435\u0442 \u043D\u0430 \u0441\u0432\u044F\u0437\u0430\u043D\u043D\u044B\u0439 URL \u0434\u043B\u044F \u0435\u0434\u0438\u043D\u043E\u0439 \u0442\u043E\u0447\u043A\u0438 \u0432\u044B\u0445\u043E\u0434\u0430 \u0438\u0437 \u0441\u0435\u0440\u0432\u0438\u0441\u0430 \u0434\u043B\u044F \u043A\u043B\u0438\u0435\u043D\u0442\u043E\u0432. \u0412\u044B \u043C\u043E\u0436\u0435\u0442\u0435 \u043E\u0441\u0442\u0430\u0432\u0438\u0442\u044C \u044D\u0442\u043E \u043F\u043E\u043B\u0435 \u043F\u0443\u0441\u0442\u044B\u043C, \u0435\u0441\u043B\u0438 \u0438\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0435\u0442\u0435 \u0440\u0430\u0437\u043B\u0438\u0447\u043D\u044B\u0435 \u0441\u0432\u044F\u0437\u0438. diff --git a/themes/src/main/resources/theme/base/admin/messages/messages_en.properties b/themes/src/main/resources/theme/base/admin/messages/messages_en.properties index 345cb2503c..f4044ab7df 100644 --- a/themes/src/main/resources/theme/base/admin/messages/messages_en.properties +++ b/themes/src/main/resources/theme/base/admin/messages/messages_en.properties @@ -14,4 +14,11 @@ ldapErrorCantWriteOnlyForReadOnlyLdap=Can't set write only when LDAP provider mo ldapErrorCantWriteOnlyAndReadOnly=Can't set write-only and read-only together clientRedirectURIsFragmentError=Redirect URIs must not contain an URI fragment -clientRootURLFragmentError=Root URL must not contain an URL fragment \ No newline at end of file +clientRootURLFragmentError=Root URL must not contain an URL fragment + +pairwiseMalformedClientRedirectURI=Client contained an invalid redirect URI. +pairwiseClientRedirectURIsMissingHost=Client redirect URIs must contain a valid host component. +pairwiseClientRedirectURIsMultipleHosts=Without a configured Sector Identifier URI, client redirect URIs must not contain multiple host components. +pairwiseMalformedSectorIdentifierURI=Malformed Sector Identifier URI. +pairwiseFailedToGetRedirectURIs=Failed to get redirect URIs from the Sector Identifier URI. +pairwiseRedirectURIsMismatch=Client redirect URIs does not match redirect URIs fetched from the Sector Identifier URI. diff --git a/themes/src/main/resources/theme/base/admin/messages/messages_lt.properties b/themes/src/main/resources/theme/base/admin/messages/messages_lt.properties new file mode 100644 index 0000000000..49089257be --- /dev/null +++ b/themes/src/main/resources/theme/base/admin/messages/messages_lt.properties @@ -0,0 +1,17 @@ +invalidPasswordMinLengthMessage=Per trumpas slapta\u00c5\u00beodis: ma\u00c5\u00beiausias ilgis {0}. +invalidPasswordMinLowerCaseCharsMessage=Neteisingas slapta\u00c5\u00beodis: privaloma \u00c4\u00c6vesti {0} ma\u00c5\u00be\u00c4\u2026j\u00c4\u2026 raid\u00c4\u2122. +invalidPasswordMinDigitsMessage=Neteisingas slapta\u00c5\u00beodis: privaloma \u00c4\u00c6vesti {0} skaitmen\u00c4\u00c6. +invalidPasswordMinUpperCaseCharsMessage=Neteisingas slapta\u00c5\u00beodis: privaloma \u00c4\u00c6vesti {0} did\u00c5\u00bei\u00c4\u2026j\u00c4\u2026 raid\u00c4\u2122. +invalidPasswordMinSpecialCharsMessage=Neteisingas slapta\u00c5\u00beodis: privaloma \u00c4\u00c6vesti {0} special\u00c5\u00b3 simbol\u00c4\u00c6. +invalidPasswordNotUsernameMessage=Neteisingas slapta\u00c5\u00beodis: slapta\u00c5\u00beodis negali sutapti su naudotojo vardu. +invalidPasswordRegexPatternMessage=Neteisingas slapta\u00c5\u00beodis: slapta\u00c5\u00beodis netenkina regex taisykl\u00c4\u2014s(i\u00c5\u00b3). +invalidPasswordHistoryMessage=Neteisingas slapta\u00c5\u00beodis: slapta\u00c5\u00beodis negali sutapti su prie\u00c5\ufffd tai buvusiais {0} slapta\u00c5\u00beod\u00c5\u00beiais. + +ldapErrorInvalidCustomFilter=Sukonfig\u016Bruotas LDAP filtras neprasideda "(" ir nesibaigia ")" simboliais. +ldapErrorMissingClientId=Privaloma nurodyti kliento ID kai srities roli\u0173 susiejimas n\u0117ra nenaudojamas. +ldapErrorCantPreserveGroupInheritanceWithUIDMembershipType=Grupi\u0173 paveld\u0117jimo ir UID naryst\u0117s tipas kartu negali b\u016Bti naudojami. +ldapErrorCantWriteOnlyForReadOnlyLdap=Negalima nustatyti ra\u0161ymo r\u0117\u017Eimo kuomet LDAP teik\u0117jo r\u0117\u017Eimas ne WRITABLE +ldapErrorCantWriteOnlyAndReadOnly=Negalima nustatyti tik ra\u0161yti ir tik skaityti kartu + +clientRedirectURIsFragmentError=Nurodykite URI fragment\u0105, kurio negali b\u016Bti peradresuojamuose URI adresuose +clientRootURLFragmentError=Nurodykite URL fragment\u0105, kurio negali b\u016Bti \u0161akniniame URL adrese \ No newline at end of file diff --git a/themes/src/main/resources/theme/base/admin/messages/messages_no.properties b/themes/src/main/resources/theme/base/admin/messages/messages_no.properties new file mode 100644 index 0000000000..43cb61df08 --- /dev/null +++ b/themes/src/main/resources/theme/base/admin/messages/messages_no.properties @@ -0,0 +1,14 @@ +invalidPasswordMinLengthMessage=Ugyldig passord: minimum lengde {0}. +invalidPasswordMinLowerCaseCharsMessage=Ugyldig passord: m\u00E5 inneholde minst {0} sm\u00E5 bokstaver. +invalidPasswordMinDigitsMessage=Ugyldig passord: m\u00E5 inneholde minst {0} sifre. +invalidPasswordMinUpperCaseCharsMessage=Ugyldig passord: m\u00E5 inneholde minst {0} store bokstaver. +invalidPasswordMinSpecialCharsMessage=Ugyldig passord: m\u00E5 inneholde minst {0} spesialtegn. +invalidPasswordNotUsernameMessage=Ugyldig passord: kan ikke v\u00E6re likt brukernavn. +invalidPasswordRegexPatternMessage=Ugyldig passord: tilfredsstiller ikke kravene for passord-m\u00F8nster. +invalidPasswordHistoryMessage=Ugyldig passord: kan ikke v\u00E6re likt noen av de {0} foreg\u00E5ende passordene. + +ldapErrorInvalidCustomFilter=Tilpasset konfigurasjon av LDAP-filter starter ikke med "(" eller slutter ikke med ")". +ldapErrorMissingClientId=KlientID m\u00E5 v\u00E6re tilgjengelig i config n\u00E5r sikkerhetsdomenerollemapping ikke brukes. +ldapErrorCantPreserveGroupInheritanceWithUIDMembershipType=Ikke mulig \u00E5 bevare gruppearv og samtidig bruke UID medlemskapstype. +ldapErrorCantWriteOnlyForReadOnlyLdap=Kan ikke sette write-only n\u00E5r LDAP leverand\u00F8r-modus ikke er WRITABLE +ldapErrorCantWriteOnlyAndReadOnly=Kan ikke sette b\u00E5de write-only og read-only diff --git a/themes/src/main/resources/theme/base/admin/messages/messages_pt_BR.properties b/themes/src/main/resources/theme/base/admin/messages/messages_pt_BR.properties index e69de29bb2..b26e46508b 100644 --- a/themes/src/main/resources/theme/base/admin/messages/messages_pt_BR.properties +++ b/themes/src/main/resources/theme/base/admin/messages/messages_pt_BR.properties @@ -0,0 +1,18 @@ +#encoding: utf-8 +invalidPasswordMinLengthMessage=Senha inválida: deve conter ao menos {0} caracteres. +invalidPasswordMinLowerCaseCharsMessage=Senha inválida: deve conter ao menos {0} caracteres minúsculos. +invalidPasswordMinDigitsMessage=Senha inválida: deve conter ao menos {0} digitos numéricos. +invalidPasswordMinUpperCaseCharsMessage=Senha inválida: deve conter ao menos {0} caracteres maiúsculos. +invalidPasswordMinSpecialCharsMessage=Senha inválida: deve conter ao menos {0} caracteres especiais. +invalidPasswordNotUsernameMessage=Senha inválida: não deve ser igual ao nome de usuário. +invalidPasswordRegexPatternMessage=Senha inválida: falha ao passar por padrões. +invalidPasswordHistoryMessage=Senha inválida: não deve ser igual às últimas {0} senhas. + +ldapErrorInvalidCustomFilter=Filtro LDAP não inicia com "(" ou não termina com ")". +ldapErrorMissingClientId=ID do cliente precisa ser definido na configuração quando mapeamentos de Roles do Realm não é utilizado. +ldapErrorCantPreserveGroupInheritanceWithUIDMembershipType=Não é possível preservar herança de grupos e usar tipo de associação de UID ao mesmo tempo. +ldapErrorCantWriteOnlyForReadOnlyLdap=Não é possível definir modo de somente escrita quando o provedor LDAP não suporta escrita +ldapErrorCantWriteOnlyAndReadOnly=Não é possível definir somente escrita e somente leitura ao mesmo tempo + +clientRedirectURIsFragmentError=URIs de redirecionamento não podem conter fragmentos +clientRootURLFragmentError=URL raiz não pode conter fragmentos \ No newline at end of file diff --git a/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js b/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js index 24efd88e68..12af561667 100755 --- a/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js +++ b/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js @@ -797,6 +797,12 @@ module.controller('ClientDetailCtrl', function($scope, realm, client, templates, "RS256" ]; + $scope.requestObjectSignatureAlgorithms = [ + "any", + "none", + "RS256" + ]; + $scope.realm = realm; $scope.samlAuthnStatement = false; $scope.samlMultiValuedRoles = false; @@ -806,6 +812,7 @@ module.controller('ClientDetailCtrl', function($scope, realm, client, templates, $scope.samlEncrypt = false; $scope.samlForcePostBinding = false; $scope.samlForceNameIdFormat = false; + $scope.disableAuthorizationTab = !client.authorizationServicesEnabled; function updateProperties() { if (!$scope.client.attributes) { @@ -898,7 +905,11 @@ module.controller('ClientDetailCtrl', function($scope, realm, client, templates, } } - $scope.userInfoSignedResponseAlg = getSignatureAlgorithm('user.info.response'); + var attrVal1 = $scope.client.attributes['user.info.response.signature.alg']; + $scope.userInfoSignedResponseAlg = attrVal1==null ? 'unsigned' : attrVal1; + + var attrVal2 = $scope.client.attributes['request.object.signature.alg']; + $scope.requestObjectSignatureAlg = attrVal2==null ? 'any' : attrVal2; } if (!$scope.create) { @@ -964,23 +975,20 @@ module.controller('ClientDetailCtrl', function($scope, realm, client, templates, }; $scope.changeUserInfoSignedResponseAlg = function() { - changeSignatureAlgorithm('user.info.response', $scope.userInfoSignedResponseAlg); + if ($scope.userInfoSignedResponseAlg === 'unsigned') { + $scope.client.attributes['user.info.response.signature.alg'] = null; + } else { + $scope.client.attributes['user.info.response.signature.alg'] = $scope.userInfoSignedResponseAlg; + } }; - function changeSignatureAlgorithm(attrPrefix, attrValue) { - var attrName = attrPrefix + '.signature.alg'; - if (attrValue === 'unsigned') { - $scope.client.attributes[attrName] = null; + $scope.changeRequestObjectSignatureAlg = function() { + if ($scope.requestObjectSignatureAlg === 'any') { + $scope.client.attributes['request.object.signature.alg'] = null; } else { - $scope.client.attributes[attrName] = attrValue; + $scope.client.attributes['request.object.signature.alg'] = $scope.requestObjectSignatureAlg; } - } - - function getSignatureAlgorithm(attrPrefix) { - var attrName = attrPrefix + '.signature.alg'; - var attrVal = $scope.client.attributes[attrName]; - return attrVal==null ? 'unsigned' : attrVal; - } + }; $scope.$watch(function() { return $location.path(); @@ -1695,6 +1703,12 @@ module.controller('ClientProtocolMapperCtrl', function($scope, realm, serverInfo mapper = angular.copy($scope.mapper); $location.url("/realms/" + realm.realm + '/clients/' + client.id + "/mappers/" + $scope.model.mapper.id); Notifications.success("Your changes have been saved."); + }, function(error) { + if (error.status == 400 && error.data.error_description) { + Notifications.error(error.data.error_description); + } else { + Notifications.error('Unexpected error when updating protocol mapper'); + } }); }; diff --git a/themes/src/main/resources/theme/base/admin/resources/js/services.js b/themes/src/main/resources/theme/base/admin/resources/js/services.js index 6d8fdcdcf1..a063143fff 100755 --- a/themes/src/main/resources/theme/base/admin/resources/js/services.js +++ b/themes/src/main/resources/theme/base/admin/resources/js/services.js @@ -274,7 +274,7 @@ module.service('ServerInfo', function($resource, $q, $http) { var delay = $q.defer(); $http.get(authUrl + '/admin/serverinfo').success(function(data) { - info = data; + angular.copy(data, info); delay.resolve(info); }); diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/authz/policy/provider/resource-server-policy-drools-detail.html b/themes/src/main/resources/theme/base/admin/resources/partials/authz/policy/provider/resource-server-policy-drools-detail.html index a121f17255..ba53e0b97e 100644 --- a/themes/src/main/resources/theme/base/admin/resources/partials/authz/policy/provider/resource-server-policy-drools-detail.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/authz/policy/provider/resource-server-policy-drools-detail.html @@ -88,10 +88,10 @@ {{:: 'authz-policy-drools-update-period.tooltip' | translate}} diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-clustering.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-clustering.html index 3b6c48cc97..4a6312b4d7 100644 --- a/themes/src/main/resources/theme/base/admin/resources/partials/client-clustering.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-clustering.html @@ -19,10 +19,10 @@ max="31536000" data-ng-model="client.nodeReRegistrationTimeout" id="nodeReRegistrationTimeout" name="nodeReRegistrationTimeout"/> diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-detail.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-detail.html index af10a22d03..9fb3a2d5be 100755 --- a/themes/src/main/resources/theme/base/admin/resources/partials/client-detail.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-detail.html @@ -110,7 +110,7 @@ -
+
{{:: 'authz-authorization-services-enabled.tooltip' | translate}}
@@ -348,6 +348,19 @@
{{:: 'user-info-signed-response-alg.tooltip' | translate}}
+
+ +
+
+ +
+
+ {{:: 'request-object-signature-alg.tooltip' | translate}} +
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/federated-ldap.html b/themes/src/main/resources/theme/base/admin/resources/partials/federated-ldap.html index 49d1fae6fe..d2a75c22b6 100755 --- a/themes/src/main/resources/theme/base/admin/resources/partials/federated-ldap.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/federated-ldap.html @@ -232,7 +232,7 @@ {{:: 'debug.tooltip' | translate}}
- +
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/group-role-mappings.html b/themes/src/main/resources/theme/base/admin/resources/partials/group-role-mappings.html index d49561e58d..bcb3150df3 100755 --- a/themes/src/main/resources/theme/base/admin/resources/partials/group-role-mappings.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/group-role-mappings.html @@ -84,7 +84,8 @@
- + + {{:: 'group.effective-roles-client.tooltip' | translate}}
-
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/realm-events-config.html b/themes/src/main/resources/theme/base/admin/resources/partials/realm-events-config.html index 8f36f6a621..a304784b2a 100755 --- a/themes/src/main/resources/theme/base/admin/resources/partials/realm-events-config.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/realm-events-config.html @@ -17,14 +17,12 @@
- + {{:: 'event-listeners.tooltip' | translate}}
- -
@@ -33,41 +31,37 @@
+ {{:: 'login.save-events.tooltip' | translate}}
-
- + {{:: 'saved-types.tooltip' | translate}}
- -
+ {{:: 'clear-events.tooltip' | translate}}
-
-
+
-
+ {{:: 'events.expiration.tooltip' | translate}} +
-
-
- + + + -
@@ -78,28 +72,26 @@
+ {{:: 'admin.save-events.tooltip' | translate}}
- -
+ {{:: 'include-representation.tooltip' | translate}}
- -
+ {{:: 'clear-admin-events.tooltip' | translate}}
-
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-oidc.html b/themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-oidc.html index 1db4c6af62..bbbf9b9ba5 100755 --- a/themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-oidc.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-oidc.html @@ -34,13 +34,6 @@
{{:: 'identity-provider.enabled.tooltip' | translate}}
-
- -
- -
- {{:: 'identity-provider.authenticate-by-default.tooltip' | translate}} -
@@ -118,13 +111,13 @@
{{:: 'identity-provider.logout-url.tooltip' | translate}}
-
- -
- -
- -
+
+ +
+ +
+ {{:: 'backchannel-logout.tooltip' | translate}} +
@@ -167,9 +160,9 @@
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-saml.html b/themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-saml.html index aa94559e03..eaf4439f03 100755 --- a/themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-saml.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-saml.html @@ -31,13 +31,6 @@
{{:: 'identity-provider.enabled.tooltip' | translate}} -
- -
- -
- {{:: 'identity-provider.authenticate-by-default.tooltip' | translate}} -
@@ -109,13 +102,13 @@
{{:: 'saml.single-logout-service-url.tooltip' | translate}}
-
- -
- -
- -
+
+ +
+ +
+ {{:: 'backchannel-logout.tooltip' | translate}} +
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-social.html b/themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-social.html index 9599d98905..6c7ceb79e6 100755 --- a/themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-social.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-social.html @@ -70,13 +70,6 @@
{{:: 'trust-email.tooltip' | translate}}
-
- -
- -
- {{:: 'identity-provider.authenticate-by-default.tooltip' | translate}} -
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider.html b/themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider.html index 1b1ae9611a..2f5ed400b5 100755 --- a/themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider.html @@ -1,7 +1,32 @@

{{:: 'identity-providers' | translate}}

+
+
+ +
+

+ Identity Providers +

+

+ Through Identity Brokering it's easy to allow users to authenticate to Keycloak using external Identity Providers or Social Networks.
We have built-in support for OpenID Connect and SAML 2.0 as well as a number of social networks such as Google, GitHub, Facebook and Twitter. +

+

To get started select a provider from the dropdown below:

+
+
+
+
+ +
+
+
+
-
+
+
@@ -18,7 +43,7 @@ - + @@ -26,7 +51,7 @@ - + + + + + diff --git a/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-client.html b/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-client.html index a0d88c645d..e2d5db2665 100755 --- a/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-client.html +++ b/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-client.html @@ -19,7 +19,7 @@ {{:: 'scope' | translate}}{{:: 'scope.tooltip' | translate}} -
  • {{:: 'authz-authorization' | translate}}
  • +
  • {{:: 'authz-authorization' | translate}}
  • {{:: 'revocation' | translate}}
  • diff --git a/themes/src/main/resources/theme/base/admin/theme.properties b/themes/src/main/resources/theme/base/admin/theme.properties index 6b8008cc09..46cee85718 100644 --- a/themes/src/main/resources/theme/base/admin/theme.properties +++ b/themes/src/main/resources/theme/base/admin/theme.properties @@ -1,2 +1,2 @@ import=common/keycloak -locales=ca,de,en,es,fr,it,ja,pt-BR,ru \ No newline at end of file +locales=ca,de,en,es,fr,it,ja,lt,no,pt-BR,ru \ No newline at end of file diff --git a/themes/src/main/resources/theme/base/email/messages/messages_lt.properties b/themes/src/main/resources/theme/base/email/messages/messages_lt.properties new file mode 100644 index 0000000000..f0eb51c5b7 --- /dev/null +++ b/themes/src/main/resources/theme/base/email/messages/messages_lt.properties @@ -0,0 +1,24 @@ +emailVerificationSubject=El. pa\u0161to patvirtinimas +emailVerificationBody=Paskyra {2} sukurta naudojant \u0161\u012F el. pa\u0161to adres\u0105. Jei tau buvote J\u016Bs, tuomet paspauskite \u017Eemiau esan\u010Di\u0105 nuorod\u0105\n\n{0}\n\n\u0160i nuoroda galioja {1} min.\n\nJei paskyros nek\u016Br\u0117te, tuomet ignuoruokite \u0161\u012F lai\u0161k\u0105. +emailVerificationBodyHtml=

    Paskyra {2} sukurta naudojant \u0161\u012F el. pa\u0161to adres\u0105. Jei tau buvote J\u016Bs, tuomet paspauskite \u017Eemiau esan\u010Di\u0105 nuorod\u0105

    {0}

    \u0160i nuoroda galioja {1} min.

    nJei paskyros nek\u016Br\u0117te, tuomet ignuoruokite \u0161\u012F lai\u0161k\u0105.

    +identityProviderLinkSubject=S\u0105saja {0} +identityProviderLinkBody=Ka\u017Eas pageidauja susieti J\u016Bs\u0173 "{1}" paskyr\u0105 su "{0}" {2} naudotojo paskyr\u0105. Jei tai buvote J\u016Bs, tuomet paspauskite \u017Eemiau esan\u010Di\u0105 nuorod\u0105 nor\u0117dami susieti paskyras\n\n{3}\n\n\u0160i nuoroda galioja {4} min.\n\nJei paskyr\u0173 susieti nenorite, tuomet ignoruokite \u0161\u012F lai\u0161k\u0105. Jei paskyras susiesite, tuomet prie {1} gal\u0117siste prisijungti per {0}. +identityProviderLinkBodyHtml=

    \u017Eas pageidauja susieti J\u016Bs\u0173 {1} paskyr\u0105 su {0} {2} naudotojo paskyr\u0105. Jei tai buvote J\u016Bs, tuomet paspauskite \u017Eemiau esan\u010Di\u0105 nuorod\u0105 nor\u0117dami susieti paskyras

    {3}

    \u0160i nuoroda galioja {4} min.

    Jei paskyr\u0173 susieti nenorite, tuomet ignoruokite \u0161\u012F lai\u0161k\u0105. Jei paskyras susiesite, tuomet prie {1} gal\u0117siste prisijungti per {0}.

    +passwordResetSubject=Slapta\u017Eod\u017Eio atk\u016Brimas +passwordResetBody=Ka\u017Ekas pageidauja pakeisti J\u016Bs\u0173 paskyros {2} slapta\u017Eod\u012F. Jei tai buvote J\u016Bs, tuomet paspauskite \u017Eemiau esan\u010Di\u0105 nuorod\u0105 slapta\u017Eod\u017Eio pakeitimui.\n\n{0}\n\n\u0160i nuoroda ir kodas galioja {1} min.\n\nJei nepageidajate keisti slapta\u017Eod\u017Eio, tuomet ignoruokite \u0161\u012F lai\u0161k\u0105 ir niekas nebus pakeista. +passwordResetBodyHtml=

    Ka\u017Ekas pageidauja pakeisti J\u016Bs\u0173 paskyros {2} slapta\u017Eod\u012F. Jei tai buvote J\u016Bs, tuomet paspauskite \u017Eemiau esan\u010Di\u0105 nuorod\u0105 slapta\u017Eod\u017Eio pakeitimui.

    {0}

    \u0160i nuoroda ir kodas galioja {1} min.

    Jei nepageidajate keisti slapta\u017Eod\u017Eio, tuomet ignoruokite \u0161\u012F lai\u0161k\u0105 ir niekas nebus pakeista.

    +executeActionsSubject=Atnaujinkite savo paskyr\u0105 +executeActionsBody=Sistemos administratorius pageidauja, kad J\u016Bs atnaujintum\u0117te savo {2} paskyr\u0105. Paspauskite \u017Eemiau esan\u010Di\u0105 nuorod\u0105 paskyros duomen\u0173 atnaujinimui.\n\n{0}\n\n\u0160i nuoroda galioja {1} min.\n\nJei J\u016Bs neasate tikri, kad tai administratoriaus pageidavimas, tuomet ignoruokite \u0161\u012F lai\u0161k\u0105 ir niekas nebus pakeista. +executeActionsBodyHtml=

    Sistemos administratorius pageidauja, kad J\u016Bs atnaujintum\u0117te savo {2} paskyr\u0105. Paspauskite \u017Eemiau esan\u010Di\u0105 nuorod\u0105 paskyros duomen\u0173 atnaujinimui.

    {0}

    \u0160i nuoroda galioja {1} min.

    Jei J\u016Bs neasate tikri, kad tai administratoriaus pageidavimas, tuomet ignoruokite \u0161\u012F lai\u0161k\u0105 ir niekas nebus pakeista.

    +eventLoginErrorSubject=Bandymas prisijungti prie J\u016Bs\u0173 paskyros +eventLoginErrorBody=Bandymas prisijungti prie J\u016Bs\u0173 paskyros {0} i\u0161 {1} nes\u012Fkmingas. Jei tai nebuvote J\u016Bs, tuomet susisiekite su administratoriumi +eventLoginErrorBodyHtml=

    Bandymas prisijungti prie J\u016Bs\u0173 paskyros {0} i\u0161 {1} nes\u012Fkmingas. Jei tai nebuvote J\u016Bs, tuomet susisiekite su administratoriumi

    +eventRemoveTotpSubject=TOTP pa\u0161alinimas +eventRemoveTotpBody=Ka\u017Ekas pageidauja atsieti TOPT J\u016Bs\u0173 {1} paskyroje su {0}. Jei tai nebuvote J\u016Bs, tuomet susisiekite su administratoriumi +eventRemoveTotpBodyHtml=

    Ka\u017Ekas pageidauja atsieti TOPT J\u016Bs\u0173 {1} paskyroje su {0}. Jei tai nebuvote J\u016Bs, tuomet susisiekite su administratoriumi

    +eventUpdatePasswordSubject=Slapta\u017Eod\u017Eio atnaujinimas +eventUpdatePasswordBody=J\u016Bs\u0173 slapta\u017Eodis J\u016Bs\u0173 {1} paskyroje su {0} buvo pakeisas. Jei tai nebuvote J\u016Bs, tuomet susisiekite su administratoriumi +eventUpdatePasswordBodyHtml=

    J\u016Bs\u0173 {1} paskyroje su {0. Jei tai nebuvote J\u016Bs, tuomet susisiekite su administratoriumi

    +eventUpdateTotpSubject=TOTP atnaujinimas +eventUpdateTotpBody=TOTP J\u016Bs\u0173 {1} paskyroje su {0} buvo atnaujintas. Jei tai nebuvote J\u016Bs, tuomet susisiekite su administratoriumi +eventUpdateTotpBodyHtml=

    TOTP J\u016Bs\u0173 {1} paskyroje su {0} buvo atnaujintas. Jei tai nebuvote J\u016Bs, tuomet susisiekite su administratoriumi

    diff --git a/themes/src/main/resources/theme/base/email/messages/messages_no.properties b/themes/src/main/resources/theme/base/email/messages/messages_no.properties new file mode 100644 index 0000000000..32334e9d8d --- /dev/null +++ b/themes/src/main/resources/theme/base/email/messages/messages_no.properties @@ -0,0 +1,24 @@ +emailVerificationSubject=Bekreft e-postadresse +emailVerificationBody=Noen har opprettet en {2} konto med denne e-postadressen. Hvis dette var deg, klikk p\u00E5 lenken nedenfor for \u00E5 bekrefte e-postadressen din\n\n{0}\n\nDenne lenken vil utl\u00F8pe om {1} minutter.\n\nHvis du ikke opprettet denne kontoen, vennligst ignorer denne meldingen. +emailVerificationBodyHtml=

    Noen har opprettet en {2} konto med denne e-postadressen. Hvis dette var deg, klikk p\u00E5 lenken nedenfor for \u00E5 bekrefte e-postadressen din

    {0}

    Denne lenken vil utl\u00F8pe om {1} minutter.

    Hvis du ikke opprettet denne kontoen, vennligst ignorer denne meldingen.

    +identityProviderLinkSubject=Lenke {0} +identityProviderLinkBody=Noen vil koble din {1} konto med {0} konto til bruker {2}. Hvis dette var deg, klikk p\u00E5 lenken nedenfor for \u00E5 koble kontoene\n\n{3}\n\nDenne lenken vil utl\u00F8pe om {4} minutter\n\nHvis du ikke vil koble kontoene, vennligst ignorer denne meldingen. Hvis du kobler kontoene sammen vil du kunne logge inn til {1} gjennom {0}. +identityProviderLinkBodyHtml=

    Noen vil koble din {1} konto med {0} konto til bruker {2}. Hvis dette var deg, klikk p\u00E5 lenken nedenfor for \u00E5 koble kontoene.

    {3}

    Denne lenken vil utl\u00F8pe om {4} minutter.

    Hvis du ikke vil koble kontoene, vennligst ignorer denne meldingen. Hvis du kobler kontoene sammen vil du kunne logge inn til {1} gjennom {0}.

    +passwordResetSubject=Tilbakestill passord +passwordResetBody=Noen har bedt om \u00E5 endre innloggingsdetaljene til din konto {2}. Hvis dette var deg, klikk p\u00E5 lenken nedenfor for \u00E5 tilbakestille dem.\n\n{0}\n\nDenne lenken vil utl\u00F8pe om {1} minutter.\n\nHvis du ikke vil tilbakestille din innloggingsdata, vennligst ignorer denne meldingen og ingenting vil bli endret. +passwordResetBodyHtml=

    Noen har bedt om \u00E5 endre innloggingsdetaljene til din konto {2}. Hvis dette var deg, klikk p\u00E5 lenken nedenfor for \u00E5 tilbakestille dem.

    {0}

    Denne lenken vil utl\u00F8pe om {1} minutter.

    Hvis du ikke vil tilbakestille din innloggingsdata, vennligst ignorer denne meldingen og ingenting vil bli endret.

    +executeActionsSubject=Oppdater kontoen din +executeActionsBody=Administrator har anmodet at du oppdaterer din {2} konto. Klikk p\u00E5 lenken nedenfor for \u00E5 starte denne prosessen\n\n{0}\n\nDenne lenken vil utl\u00F8pe om {1} minutter.\n\nHvis du ikke var klar over at administrator har bedt om dette, vennligst ignorer denne meldingen og ingenting vil bli endret. +executeActionsBodyHtml=

    Administrator har anmodet at du oppdaterer din {2} konto. Klikk p\u00E5 linken nedenfor for \u00E5 starte denne prosessen.

    {0}

    Denne lenken vil utl\u00F8pe om {1} minutter.

    Hvis du ikke var klar over at administrator har bedt om dette, ignorer denne meldingen og ingenting vil bli endret.

    +eventLoginErrorSubject=Innlogging feilet +eventLoginErrorBody=Et mislykket innloggingsfors\u00F8k ble oppdaget p\u00E5 din konto p\u00E5 {0} fra {1}. Hvis dette ikke var deg, vennligst kontakt administrator. +eventLoginErrorBodyHtml=

    Et mislykket innloggingsfors\u00F8k ble oppdaget p\u00E5 din konto p\u00E5 {0} fra {1}. Hvis dette ikke var deg, vennligst kontakt administrator.

    +eventRemoveTotpSubject=Fjern engangskode +eventRemoveTotpBody=Engangskode ble fjernet fra kontoen din p\u00E5 {0} fra {1}. Hvis dette ikke var deg, vennligst kontakt administrator. +eventRemoveTotpBodyHtml=

    Engangskode ble fjernet fra kontoen din p\u00E5 {0} fra {1}. Hvis dette ikke var deg, vennligst kontakt administrator.

    +eventUpdatePasswordSubject=Oppdater passord +eventUpdatePasswordBody=Ditt passord ble endret i {0} fra {1}. Hvis dette ikke var deg, vennligst kontakt administrator. +eventUpdatePasswordBodyHtml=

    Ditt passord ble endret i {0} fra {1}. Hvis dette ikke var deg, vennligst kontakt administrator.

    +eventUpdateTotpSubject=Oppdater engangskode +eventUpdateTotpBody=Engangskode ble oppdatert for kontoen din p\u00E5 {0} fra {1}. Hvis dette ikke var deg, vennligst kontakt administrator. +eventUpdateTotpBodyHtml=

    Engangskode ble oppdatert for kontoen din p\u00E5 {0} fra {1}. Hvis dette ikke var deg, vennligst kontakt administrator.

    diff --git a/themes/src/main/resources/theme/base/email/theme.properties b/themes/src/main/resources/theme/base/email/theme.properties index 2f853e89da..2cf724db33 100644 --- a/themes/src/main/resources/theme/base/email/theme.properties +++ b/themes/src/main/resources/theme/base/email/theme.properties @@ -1 +1 @@ -locales=ca,de,en,es,fr,it,ja,pt-BR,ru \ No newline at end of file +locales=ca,de,en,es,fr,it,ja,lt,no,pt-BR,ru \ No newline at end of file diff --git a/themes/src/main/resources/theme/base/login/login-reset-password.ftl b/themes/src/main/resources/theme/base/login/login-reset-password.ftl index 404de17ea2..a561b2a65f 100755 --- a/themes/src/main/resources/theme/base/login/login-reset-password.ftl +++ b/themes/src/main/resources/theme/base/login/login-reset-password.ftl @@ -8,7 +8,7 @@
    - +
    @@ -30,4 +30,4 @@ <#elseif section = "info" > ${msg("emailInstruction")} - \ No newline at end of file + diff --git a/themes/src/main/resources/theme/base/login/messages/messages_en.properties b/themes/src/main/resources/theme/base/login/messages/messages_en.properties index 4e1bd577b2..f69733ad5b 100755 --- a/themes/src/main/resources/theme/base/login/messages/messages_en.properties +++ b/themes/src/main/resources/theme/base/login/messages/messages_en.properties @@ -215,9 +215,11 @@ locale_es=Espa\u00F1ol locale_fr=Fran\u00e7ais locale_it=Italian locale_ja=\u65E5\u672C\u8A9E +locale_no=Norsk locale_pt_BR=Portugu\u00EAs (Brasil) locale_pt-BR=Portugu\u00EAs (Brasil) locale_ru=\u0420\u0443\u0441\u0441\u043A\u0438\u0439 +locale_lt=Lietuvi\u0173 backToApplication=« Back to Application missingParameterMessage=Missing parameters\: {0} @@ -226,4 +228,4 @@ clientDisabledMessage=Client disabled. invalidParameterMessage=Invalid parameter\: {0} alreadyLoggedIn=You are already logged in. -p3pPolicy=CP="This is not a P3P policy!" \ No newline at end of file +p3pPolicy=CP="This is not a P3P policy!" diff --git a/themes/src/main/resources/theme/base/login/messages/messages_lt.properties b/themes/src/main/resources/theme/base/login/messages/messages_lt.properties new file mode 100644 index 0000000000..bffd8ed888 --- /dev/null +++ b/themes/src/main/resources/theme/base/login/messages/messages_lt.properties @@ -0,0 +1,218 @@ +doLogIn=Prisijungti +doRegister=Registruotis +doCancel=At\u0161aukti +doSubmit=Patvirtinti +doYes=Taip +doNo=Ne +doContinue=T\u0119sti +doAccept=Patvirtinti +doDecline=At\u0161aukti +doForgotPassword=Pamir\u0161ote slapta\u017Eod\u012F? +doClickHere=Spauskite \u010Dia +doImpersonate=Apsimesti kaip +kerberosNotConfigured=Kerberos nesukonfig\u016Bruotas +kerberosNotConfiguredTitle=Kerberos nesukonfig\u016Bruotas +bypassKerberosDetail=J\u016Bs neprisijung\u0119s per Kerberos arba J\u016Bs\u0173 nar\u0161ykl\u0117 nesukonfig\u016Bruota Kerberos prisijungimui. T\u0119skite ir pasirinkite kit\u0105 prisijungimo b\u016Bd\u0105 +kerberosNotSetUp=Kerberos nesukonfig\u016Bruotas. J\u016Bs negalite prisijungti. +registerWithTitle=Registruotis su {0} +registerWithTitleHtml={0} +loginTitle=Prisijungti su {0} +loginTitleHtml={0} +impersonateTitle=Apsimesti kaip naudotojas {0} +impersonateTitleHtml=Apsimesti kaip {0} +realmChoice=Sritis +unknownUser=Ne\u017Einomas naudotojas +loginTotpTitle=Mobilaus autentifikatoriaus nustatymas +loginProfileTitle=Atnaujinti paskyros informacij\u0105 +loginTimeout=U\u017Etrukote per ilgai. Prisijungimo procesas pradedamas i\u0161 naujo. +oauthGrantTitle=Suteitikti prieig\u0105 +oauthGrantTitleHtml={0} +errorTitle=Atsipra\u0161ome ... +errorTitleHtml=Atsipra\u0161ome ... +emailVerifyTitle=El. pa\u0161to adreso patvirtinimas +emailForgotTitle=Pamir\u0161ote slapta\u017Eod\u012F? +updatePasswordTitle=Atnaujinti slapta\u017Eod\u012F +codeSuccessTitle=S\u0117km\u0117 +codeErrorTitle=Klaidos kodas\: {0} + +termsTitle=Naudojimo s\u0105lygos +termsTitleHtml=Naudojimo s\u0105lygos +termsText=

    Naudojimo s\u0105lygos nenurodytos

    + +recaptchaFailed=Recaptcha neteisingas +recaptchaNotConfigured=Reikalingas Recaptcha nesukonfig\u016Bruotas +consentDenied=Prieiga draud\u017Eiama. + +noAccount=Dar neturite paskyros? +username=Naudotojo vardas +usernameOrEmail=Naudotojo vardas arba el. pa\u0161to adresas +firstName=Vardas +givenName=Vardas +fullName=Pavard\u0117 +lastName=Pavard\u0117 +familyName=Pavard\u0117 +email=El. pa\u0161tas +password=Slapta\u017Eodis +passwordConfirm=Pakartotas slapta\u017Eodis +passwordNew=Naujas slapta\u017Eodis +passwordNewConfirm=Pakartotas naujas slapta\u017Eodis +rememberMe=Prisiminti mane +authenticatorCode=Vienkartinis kodas +address=Adresas +street=Gatv\u0117 +locality=Miestas arba vietov\u0117 +region=Rajonas +postal_code=Pa\u0161to kodas +country=\u0160alis +emailVerified=El. pa\u0161to adresas patvirtintas +gssDelegationCredential=GSS prisijungimo duomen\u0173 delegavimas + +loginTotpStep1=\u012Ediekite FreeOTP arba Google Authenticator savo \u012Frenginyje. Program\u0117l\u0117s prieinamos Google Play ir Apple App Store. +loginTotpStep2=Atidarykite program\u0117l\u0119 ir nuskenuokite barkod\u0105 arba \u012Fveskite kod\u0105. +loginTotpStep3=\u012Eveskite program\u0117l\u0117je sugeneruot\u0105 vien\u0105 kart\u0105 galiojant\u012F kod\u0105 ir paspauskite Saugoti nor\u0117dami prisijungti. +loginTotpOneTime=Vienkartinis kodas + +oauthGrantRequest=Ar J\u016Bs suteikiate \u0161ias prieigos teises? +inResource=\u012F + +emailVerifyInstruction1=El. pa\u0161tas su instrukcijomis ir patvirtinimo nuoroda nusi\u0173sti \u012F J\u016Bs\u0173 el. pa\u0161t\u0105. +emailVerifyInstruction2=El. pa\u0161tu negavote patvirtinimo kodo? +emailVerifyInstruction3=pakartotoinai si\u0173sti el. lai\u0161k\u0105. + +emailLinkIdpTitle=Susieti {0} +emailLinkIdp1=El. pa\u0161to lai\u0161kas su instrukcijomis susieti {0} paskyr\u0105 {1} su {2} buvo nusi\u0173stas. +emailLinkIdp2=Negavote patvirtinimo kodo el. pa\u0161tu? +emailLinkIdp3=pakartotoinai si\u0173sti el. lai\u0161k\u0105. + +backToLogin=« Gr\u012F\u017Eti \u012F prisijungimo lang\u0105 + +emailInstruction=\u012Eveskite naudotojo vard\u0105 arba slapta\u017Eod\u012F ir slapta\u017Eod\u017Eio pakeitimo instrukcijos bus atsi\u0173stos Jums el. pa\u0161tu + +copyCodeInstruction=Nukopijuokite \u0161\u012F kod\u0105 \u012F J\u016Bs\u0173 program\u0105: + +personalInfo=Asmenin\u0117 informacija: +role_admin=Administratorius +role_realm-admin=Srities administravimas +role_create-realm=Kurti srit\u012F +role_create-client=Kurti program\u0105 +role_view-realm=Per\u017Ei\u016Br\u0117ti srit\u012F +role_view-users=Per\u017Ei\u016Br\u0117ti naudotojus +role_view-applications=Per\u017Ei\u016Br\u0117ti programas +role_view-clients=Per\u017Ei\u016Br\u0117ti klientines programas +role_view-events=Per\u017Ei\u016Br\u0117ti \u012Fvyki\u0173 \u017Eurnal\u0105 +role_view-identity-providers=Per\u017Ei\u016Br\u0117ti tapatyb\u0117s teik\u0117jus +role_manage-realm=Valdyti sritis +role_manage-users=Valdyti naudotojus +role_manage-applications=Valdyti programas +role_manage-identity-providers=Valdyti tapatyb\u0117s teik\u0117jus +role_manage-clients=Valdyti programas +role_manage-events=Valdyti \u012Fvykius +role_view-profile=Per\u017Ei\u016Br\u0117ti paskyr\u0105 +role_manage-account=Valdyti paskyr\u0105 +role_read-token=Skaityti prieigos rak\u0161\u0105 +role_offline-access=Darbas neprisijungus +client_account=Paskyra +client_security-admin-console=Saugumo administravimo konsol\u0117 +client_admin-cli=Administravimo CLI +client_realm-management=Srities valdymas +client_broker=Tarpininkas + +invalidUserMessage=Neteisingas naudotojo vardas arba slapta\u017Eodis. +invalidEmailMessage=Neteisingas el. pa\u0161to adresas. +accountDisabledMessage=Paskyros galiojimas sustabdytas, kreipkit\u0117s \u012F administratori\u0173. +accountTemporarilyDisabledMessage=Paskyros galiojimas laikinai sustabdytas. Kreipkit\u0117s \u012F administratori\u0173 arba pabandykite v\u0117liau. +expiredCodeMessage=Prisijungimo laikas baig\u0117si. Bandykite dar kart\u0105. + +missingFirstNameMessage=Pra\u0161ome \u012Fvesti vard\u0105. +missingLastNameMessage=Pra\u0161ome \u012Fvesti pavard\u0119. +missingEmailMessage=Pra\u0161ome \u012Fvesti el. pa\u0161to adres\u0105. +missingUsernameMessage=Pra\u0161ome \u012Fvesti naudotojo vard\u0105. +missingPasswordMessage=Pra\u0161ome \u012Fvesti slapta\u017Eod\u012F. +missingTotpMessage=Pra\u0161ome \u012Fvesti autentifikacijos kod\u0105. +notMatchPasswordMessage=Slapta\u017Eod\u017Eiai nesutampa. + +invalidPasswordExistingMessage=Neteisingas dabartinis slapta\u017Eodis. +invalidPasswordConfirmMessage=Pakartotas slapta\u017Eodis nesutampa. +invalidTotpMessage=Neteisingas autentifikacijos kodas. + +usernameExistsMessage=Toks naudotojas jau egzistuoja. +emailExistsMessage=El. pa\u0161to adresas jau egzistuoja. + +federatedIdentityExistsMessage=Naudotojas {0} {1} jau egzistuoja. Pra\u0161ome prsijungti prie naudotoj\u0173 valdymo posistem\u0117s paskyr\u0173 susiejimui. + +confirmLinkIdpTitle=Paskyra jau egzistuoja +federatedIdentityConfirmLinkMessage=Naudotojas {0} {1} jau egzistuoja. Ar t\u0119sti? +federatedIdentityConfirmReauthenticateMessage=Prisijunkite prie {0} nor\u0117dami susieti paskyr\u0105 su {1} +confirmLinkIdpReviewProfile=Per\u017Ei\u016Br\u0117ti naudotojo profilio informacij\u0105 +confirmLinkIdpContinue=Susieti su egzistuojan\u010Dia paskyra + +configureTotpMessage=Paskyros aktyvavimui Jums reikalingas Mobilus autentifikatorius. +updateProfileMessage=Paskyros aktyvavimui Jums reikia atnaujinti profilio informacij\u0105. +updatePasswordMessage=Paskyros aktyvavimui Jums reikia pakeisti slapta\u017Eod\u012F. +verifyEmailMessage=Paskyros aktyvavimui Jums reikia patvirtinti el. pa\u0161to adres\u0105. +linkIdpMessage=El. pa\u0161to adreso susiejimui su J\u016Bsu paskyra {0} reikalingas patvirtinimas. + +emailSentMessage=Netrukus tur\u0117tum\u0117te gauti el. pa\u0161to adres\u0105 su instrukcijomis. +emailSendErrorMessage=Klaida siun\u010Diant el. pa\u0161t\u0105, bandykite v\u0117liau. + +accountUpdatedMessage=J\u0173s\u0173 paskyros informacija atnaujinta. +accountPasswordUpdatedMessage=J\u016Bs\u0173 slapta\u017Eodis pakeistas. + +noAccessMessage=Prieiga negalima + +invalidPasswordMinLengthMessage=Neteisingas slapta\u017Eodis: privalomi bent {0} simboliai. +invalidPasswordMinDigitsMessage=Neteisingas slapta\u017Eodis: privalomi bent {0} skaitmenys. +invalidPasswordMinLowerCaseCharsMessage=Neteisingas slapta\u017Eodis: privalomos bent {0} ma\u017Eosios raid\u0117s. +invalidPasswordMinUpperCaseCharsMessage=Neteisingas slapta\u017Eodis: privalomos bent {0} did\u017Eiosios raid\u0117s. +invalidPasswordMinSpecialCharsMessage=Neteisingas slapta\u017Eodis: privalomi bent {0} special\u016Bs simboliai. +invalidPasswordNotUsernameMessage=Neteisingas slapta\u017Eodis: negali sutapti su naudotojo vardu. +invalidPasswordRegexPatternMessage=Neteisingas slapta\u017Eodis: neatitinka regexp taisykl\u0117s. +invalidPasswordHistoryMessage=Neteisingas slapta\u017Eodis: negali sutapti su prie\u0161 tai naudotais {0} slapta\u017Eod\u017Eiais. + +failedToProcessResponseMessage=Klaida apdorojant atsakym\u0105 +httpsRequiredMessage=Privalomas HTTPS +realmNotEnabledMessage=Srities galiojimas i\u0161jungtas +invalidRequestMessage=Neteisinga u\u017Eklausa +failedLogout=Nepavyko u\u017Ebaigti sesijos +unknownLoginRequesterMessage=Ne\u017Einomas prisijungimo pra\u0161ytojas +loginRequesterNotEnabledMessage=Prisijungimo pra\u0161ytojo galiojimas i\u0161jungtas +bearerOnlyMessage=Programos, sukonfig\u016Bruotos tik kaip perdav\u0117jai, negali inicijuoti prisijungim\u0105 per nar\u0161ykl\u0119. +standardFlowDisabledMessage=Su pateiktu atsakymo tipu prisijungimas per nar\u0161ykl\u0119 \u0161iam klientui negalimas. \u0160iam klientui ne\u012Fgalinta standartin\u0117 seka. +implicitFlowDisabledMessage=Su pateiktu atsakymo tipu prisijungimas per nar\u0161ykl\u0119 \u0161iam klientui negalimas. \u0160iam klientui ne\u012Fgalinta i\u0161reik\u0161tin\u0117 seka. +invalidRedirectUriMessage=Neteisinga nukreipimo nuoroda +unsupportedNameIdFormatMessage=Nepalaikomas NameIDFormat +invalidRequesterMessage=Neteisingas pra\u0161ytojas +registrationNotAllowedMessage=Registracija negalima +resetCredentialNotAllowedMessage=Prisijungimo duomen\u0173 atk\u016Brimas negalimas + +permissionNotApprovedMessage=Teis\u012F nepatvirtinta. +noRelayStateInResponseMessage=Tapatyb\u0117s teik\u0117jo atsakyme tr\u016Bksta perdavimo b\u016Bsenos. +insufficientPermissionMessage=Tr\u016Bksta teisi\u0173 tapatybi\u0173 susiejimui. +couldNotProceedWithAuthenticationRequestMessage=Nepavyksta prad\u0117ti tapatyb\u0117s teik\u0117jo autentifikacijos u\u017Eklausos. +couldNotObtainTokenMessage=Negaunamas prieigos raktas i\u0161 tapatyb\u0117s teik\u0117jo. +unexpectedErrorRetrievingTokenMessage=Prieigos rak\u0161o gavimo i\u0161 tapatyb\u0117s teik\u0117jo metu \u012Fvyko netik\u0117ta klaida. +unexpectedErrorHandlingResponseMessage=Tapatyb\u0117s teik\u0117jo atsakymo apdorojimo metu \u012Fvyko netik\u0117ta klaida. +identityProviderAuthenticationFailedMessage=Autentifikacijos klaida. Nepavyksta autentifikacija su tapatyb\u0117s teik\u0117ju. +identityProviderDifferentUserMessage=Autentifikuota kaip {0}, nors buvo tikimasi {1} +couldNotSendAuthenticationRequestMessage=Tapatyb\u0117s teik\u0117jui nepavyksta nusi\u0173sti autentifikacijos u\u017Eklausos. +unexpectedErrorHandlingRequestMessage=U\u017Eklausos tapatyb\u0117s teik\u0117jui formavimo metu \u012Fvyko netik\u0117ta klaida. +invalidAccessCodeMessage=Neteisingas prieigos kodas. +sessionNotActiveMessage=Sesija neaktyvi. +invalidCodeMessage=\u012Evyko klaida. Pra\u0161ome bandyti prisijungti dar kart\u0105. +identityProviderUnexpectedErrorMessage=Autentifikavimo su i\u0161oriniu tapatyb\u0117s teik\u0117ju metu \u012Fvyko netik\u0117ta klaida. +identityProviderNotFoundMessage=Su nurodytu identifikatoriumi nerastas tapatyb\u0117s teik\u0117jas. +identityProviderLinkSuccess=J\u016Bs\u0173 naudotojo paskyra buvo s\u0117kmingai susieta su {0} paskyra {1} . +staleCodeMessage=\u0160is puslapis nebegalioja. Pra\u0161ome gr\u012F\u017Eti \u012F program\u0105 ir bandyti prisijungti i\u0161 naujo. +realmSupportsNoCredentialsMessage=Sritis nepalaiko prisijungim\u0173 naudojant prisijungimo duomenis. +identityProviderNotUniqueMessage=Sritis palaiko daugiau nei vien\u0105 tapatyb\u0117s teik\u0117j\u0105. Negalima nustatyti kuris tapatyb\u0117s teik\u0117jas turi b\u016Bti naudojamas autentifikacijai. +emailVerifiedMessage=J\u016Bs\u0173 el. pa\u0161to adresas patvirtintas. +staleEmailVerificationLink=Nuoroda, kuri\u0105 paspaud\u0117te nebegalioja? Galb\u016Bt J\u016Bs jau patvirtinote el. pa\u0161to adres\u0105? + +backToApplication=« Gr\u012F\u017Eti \u012F program\u0105 +missingParameterMessage=Nenurodytas parametras\: {0} +clientNotFoundMessage=Nenurodytas klientas. +clientDisabledMessage=Kliento galiojimas i\u0161jungtas. +invalidParameterMessage=Neteisingas parametras\: {0} +alreadyLoggedIn=J\u016Bs jau esate prisijung\u0119. + +p3pPolicy=CP="Nurodyta reik\u0161m\u0117 n\u0117ra P3P taisykl\u0117!" \ No newline at end of file diff --git a/themes/src/main/resources/theme/base/login/messages/messages_no.properties b/themes/src/main/resources/theme/base/login/messages/messages_no.properties new file mode 100644 index 0000000000..a90af4627b --- /dev/null +++ b/themes/src/main/resources/theme/base/login/messages/messages_no.properties @@ -0,0 +1,229 @@ +doLogIn=Logg inn +doRegister=Registrer deg +doCancel=Avbryt +doSubmit=Send inn +doYes=Ja +doNo=Nei +doContinue=Fortsett +doAccept=Aksepter +doDecline=Avsl\u00E5 +doForgotPassword=Glemt passord? +doClickHere=Klikk her +doImpersonate=Utgi deg for \u00E5 v\u00E6re en annen bruker +kerberosNotConfigured=Kerberos er ikke konfigurert +kerberosNotConfiguredTitle=Kerberos er ikke konfigurert +bypassKerberosDetail=Enten er du ikke logget inn via Kerberos eller s\u00E5 st\u00F8tter ikke nettleseren innlogging med Kerberos. Vennligst klikk Fortsett for \u00E5 logge inn p\u00E5 andre m\u00E5ter +kerberosNotSetUp=Kerberos er ikke konfigurert. Du kan ikke logge inn. +registerWithTitle=Registrer deg med {0} +registerWithTitleHtml={0} +loginTitle=Logg inn p\u00E5 {0} +loginTitleHtml={0} +impersonateTitle={0} Gi deg ut for \u00E5 v\u00E6re en annen bruker +impersonateTitleHtml={0} Gi deg ut for \u00E5 v\u00E6re en annen bruker +realmChoice=Sikkerhetsdomene +unknownUser=Ukjent bruker +loginTotpTitle=Konfigurer autentifikator for mobil +loginProfileTitle=Oppdater konto +loginTimeout=Du brukte for lang tid p\u00E5 \u00E5 logge inn. Vennligst pr\u00F8v igjen. +oauthGrantTitle=Gi tilgang +oauthGrantTitleHtml={0} +errorTitle=Vi beklager... +errorTitleHtml=Vi beklager ... +emailVerifyTitle=E-postbekreftelse +emailForgotTitle=Glemt passord? +updatePasswordTitle=Oppdater passord +codeSuccessTitle=Suksesskode +codeErrorTitle=Feilkode\: {0} + +termsTitle=Vilk\u00E5r og betingelser +termsTitleHtml=Vilk\u00E5r og betingelser +termsText=

    Vilk\u00E5r og betingelser kommer

    + +recaptchaFailed=Ugyldig Bildebekreftelse +recaptchaNotConfigured=Bildebekreftelse er p\u00E5krevet, men er ikke konfigurert +consentDenied=Samtykke avsl\u00E5tt. + +noAccount=Ny bruker? +username=Brukernavn +usernameOrEmail=Brukernavn eller e-postadresse +firstName=Fornavn +givenName=Fornavn +fullName=Fullstendig navn +lastName=Etternavn +familyName=Etternavn +email=E-postadresse +password=Passord +passwordConfirm=Bekreft passord +passwordNew=Nytt passord +passwordNewConfirm=Bekreft nytt Passord +rememberMe=Husk meg +authenticatorCode=Engangskode +address=Adresse +street=Gate-/veinavn + husnummer +locality=By +region=Fylke +postal_code=Postnummer +country=Land +emailVerified=E-postadresse bekreftet +gssDelegationCredential=GSS legitimasjons-delegering + +loginTotpStep1=Installer FreeOTP eller Google Authenticator p\u00E5 din mobiltelefon. Begge applikasjoner er tilgjengelige p\u00E5 Google Play og Apple App Store. +loginTotpStep2=\u00C5pne applikasjonen og skann strekkoden eller skriv inn koden +loginTotpStep3=Skriv inn engangskoden fra applikasjonen og klikk send inn for \u00E5 fullf\u00F8re +loginTotpOneTime=Engangskode + +oauthGrantRequest=Vil du gi disse tilgangsrettighetene? +inResource=i + +emailVerifyInstruction1=En e-post med instruksjoner for \u00E5 bekrefte din e-postadresse har blitt sendt til deg. +emailVerifyInstruction2=Ikke mottatt en bekreftelseskode i e-posten vi sendte til deg? +emailVerifyInstruction3=for \u00E5 sende e-post p\u00E5 nytt. + +emailLinkIdpTitle=Lenke {0} +emailLinkIdp1=En e-post med instruksjoner for \u00E5 koble {0} konto med din {2} konto har blitt sendt til deg. +emailLinkIdp2=Ikke mottatt en bekreftelseskode i e-posten vi sendte til deg? +emailLinkIdp3=for \u00E5 sende e-post p\u00E5 nytt. + +backToLogin=« Tilbake til innlogging +emailInstruction=Skriv inn e-postadressen din og vi vil sende deg instruksjoner for hvordan du oppretter et nytt passord. + +copyCodeInstruction=Vennligst kopier denne koden og lim den inn i applikasjonen din: + +personalInfo=Personlig informasjon: +role_admin=Administrator +role_realm-admin=Administrator for sikkerhetsdomene +role_create-realm=Opprette sikkerhetsdomene +role_create-client=Opprette klient +role_view-realm=Se sikkerhetsdomene +role_view-users=Se brukere +role_view-applications=Se applikasjoner +role_view-clients=Se klienter +role_view-events=Se hendelser +role_view-identity-providers=Se identitetsleverand\u00F8rer +role_manage-realm=Administrere sikkerhetsdomene +role_manage-users=Administrere brukere +role_manage-applications=Administrere applikasjoner +role_manage-identity-providers=Administrere identitetsleverand\u00F8rer +role_manage-clients=Administrere klienter +role_manage-events=Administrere hendelser +role_view-profile=Se profil +role_manage-account=Administrere konto +role_read-token=Lese token +role_offline-access=Frakoblet tilgang +role_uma_authorization=Skaffe tillatelser +client_account=Konto +client_security-admin-console=Sikkerthetsadministrasjonskonsoll +client_realm-management=Sikkerhetsdomene-administrasjon +client_broker=Broker + +invalidUserMessage=Ugyldig brukernavn eller passord. +invalidEmailMessage=Ugyldig e-postadresse. +accountDisabledMessage=Konto er deaktivert, kontakt administrator. +accountTemporarilyDisabledMessage=Konto er midlertidig deaktivert, kontakt administrator eller pr\u00F8v p\u00E5 nytt senere. +expiredCodeMessage=Login ble tidsavbrutt. Vennligst logg inn p\u00E5 nytt. + +missingFirstNameMessage=Vennligst oppgi fornavn. +missingLastNameMessage=Vennligst oppgi etternavn. +missingEmailMessage=Vennligst oppgi e-postadresse. +missingUsernameMessage=Vennligst oppgi brukernavn. +missingPasswordMessage=Vennligst oppgi passord. +missingTotpMessage=Vennligst oppgi autentiseringskode. +notMatchPasswordMessage=Passordene er ikke like. + +invalidPasswordExistingMessage=Ugyldig eksisterende passord. +invalidPasswordConfirmMessage=Passord er ikke like. +invalidTotpMessage=Ugyldig engangskode. + +usernameExistsMessage=Brukernavnet finnes allerede. +emailExistsMessage=E-post finnes allerede. + +federatedIdentityExistsMessage=Bruker med {0} {1} finnes allerede. Vennligst logg inn p\u00E5 kontoadministratsjon for \u00E5 koble sammen kontoene. + +confirmLinkIdpTitle=Kontoen finnes allerede +federatedIdentityConfirmLinkMessage=Bruker med {0} {1} finnes allerede. Hvordan vil du fortsette? +federatedIdentityConfirmReauthenticateMessage=Bekreft at du er {0} for \u00E5 koble din konto med {1} +confirmLinkIdpReviewProfile=Se over og bekreft profil +confirmLinkIdpContinue=Legg til eksisterende konto + +configureTotpMessage=Du m\u00E5 sette opp en engangskode-generator for \u00E5 aktivere konto. +updateProfileMessage=Du m\u00E5 oppdatere brukerprofilen din for \u00E5 aktivere konto. +updatePasswordMessage=Du m\u00E5 skifte passord for \u00E5 aktivere kontoen din. +verifyEmailMessage=Du m\u00E5 bekrefte e-postadressen din for \u00E5 aktivere konto. +linkIdpMessage=You need to verify your email address to link your account with {0}. + +emailSentMessage=Du vil straks motta en e-post med ytterlige instruksjoner. +emailSendErrorMessage=Mislyktes \u00E5 sende e-post, vennligst pr\u00F8v igjen senere. + +accountUpdatedMessage=Din konto har blitt oppdatert. +accountPasswordUpdatedMessage=Ditt passord har blitt oppdatert. + +noAccessMessage=Ingen tilgang + +invalidPasswordMinLengthMessage=Ugyldig passord: minimum lengde {0}. +invalidPasswordMinDigitsMessage=Ugyldig passord: m\u00E5 inneholde minimum {0} sifre. +invalidPasswordMinLowerCaseCharsMessage=Ugyldig passord: m\u00E5 inneholde minimum {0} sm\u00E5 bokstaver. +invalidPasswordMinUpperCaseCharsMessage=Ugyldig passord: m\u00E5 inneholde minimum {0} store bokstaver. +invalidPasswordMinSpecialCharsMessage=Ugyldig passord: m\u00E5 inneholde minimum {0} spesialtegn. +invalidPasswordNotUsernameMessage=Ugyldig passord: kan ikke v\u00E6re likt brukernavn. +invalidPasswordRegexPatternMessage=Ugyldig passord: tilfredsstiller ikke kravene for passord-m\u00F8nster. +invalidPasswordHistoryMessage=Ugyldig passord: kan ikke v\u00E6re likt noen av de {0} foreg\u00E5ende passordene. + +failedToProcessResponseMessage=Kunne ikke behandle svar +httpsRequiredMessage=HTTPS p\u00E5krevd +realmNotEnabledMessage=Sikkerhetsdomene er ikke aktivert +invalidRequestMessage=Ugyldig foresp\u00F8rsel +failedLogout=Utlogging feilet +unknownLoginRequesterMessage=Ukjent anmoder for innlogging +loginRequesterNotEnabledMessage=Anmoder for innlogging er ikke aktivert +bearerOnlyMessage=Bearer-only applikasjoner har ikke lov til \u00E5 initiere innlogging via nettleser +standardFlowDisabledMessage=Klienten har ikke lov til \u00E5 initiere innlogging via nettleser med gitt response_type. Standard flow er deaktivert for denne klienten. +implicitFlowDisabledMessage=Klienten har ikke lov til \u00E5 initiere innlogging via nettleser med gitt response_type. Implicit flow er deaktivert for denne klienten. +invalidRedirectUriMessage=Ugyldig redirect uri +unsupportedNameIdFormatMessage=NameIDFormat er ikke st\u00F8ttet +invalidRequesterMessage=Ugyldig sender av foresp\u00F8rsel +registrationNotAllowedMessage=Registrering er ikke lov +resetCredentialNotAllowedMessage=Tilbakestilling av innloggingsdata er ikke lov + +permissionNotApprovedMessage=Tillatelse ikke godkjent. +noRelayStateInResponseMessage=Ingen relay state i svar fra identitetsleverand\u00F8r. +insufficientPermissionMessage=Utilstrekkelige rettigheter for \u00E5 koble identiteter. +couldNotProceedWithAuthenticationRequestMessage=Kunne ikke g\u00E5 videre med autentiseringsforesp\u00F8rsel til identitetsleverand\u00F8r. +couldNotObtainTokenMessage=Klarte ikke \u00E5 innhente token fra identitetsleverand\u00F8r. +unexpectedErrorRetrievingTokenMessage=Uventet feil ved henting av token fra identitetsleverand\u00F8r. +unexpectedErrorHandlingResponseMessage=Uventet feil ved h\u00E5ndtering av svar fra identitetsleverand\u00F8r. +identityProviderAuthenticationFailedMessage=Autentisering feilet. Kunne ikke autentisere med identitetsleverand\u00F8r. +identityProviderDifferentUserMessage= Autentisert som {0}, men forventet \u00E5 bli identifisert som {1} +couldNotSendAuthenticationRequestMessage=Kunne ikke sende autentiseringsforesp\u00F8rsel til identitetsleverand\u00F8r. +unexpectedErrorHandlingRequestMessage=Uventet feil ved h\u00E5ndtering av autentiseringsforesp\u00F8rsel til identitetsleverand\u00F8r. +invalidAccessCodeMessage=Ugyldig tilgangskode. +sessionNotActiveMessage=Sesjonen er ikke aktiv. +invalidCodeMessage=En feil oppstod, vennligst logg inn p\u00E5 nytt i din applikasjon. +identityProviderUnexpectedErrorMessage=Uventet feil ved autentisering med identitetsleverand\u00F8r +identityProviderNotFoundMessage=Kunne ikke finne en identitetsleverand\u00F8r med identifikatoren. +identityProviderLinkSuccess=Din konto ble suksessfullt koblet med {0} konto {1}. +staleCodeMessage=Denne siden er ikke lenger gyldig. Vennligst g\u00E5 tilbake til applikasjonen din og logg inn p\u00E5 nytt. +realmSupportsNoCredentialsMessage=Sikkerhetsdomene st\u00F8tter ingen legitimasjonstyper. +identityProviderNotUniqueMessage=Sikkerhetsdomene st\u00F8tter flere identitetsleverand\u00F8rer. Kunne ikke avgj\u00F8re hvilken identitetsleverand\u00F8r som burde brukes for autentisering. +emailVerifiedMessage=Din e-postadresse har blitt verifisert. +staleEmailVerificationLink=Lenken du klikket er utg\u00E5tt og er ikke lenger gyldig. Har du kanskje allerede bekreftet e-postadressen din? + +locale_ca=Catal\u00E0 +locale_de=Deutsch +locale_en=English +locale_es=Espa\u00F1ol +locale_fr=Fran\u00e7ais +locale_it=Italian +locale_ja=\u65E5\u672C\u8A9E +locale_no=Norsk +locale_pt_BR=Portugu\u00EAs (Brasil) +locale_pt-BR=Portugu\u00EAs (Brasil) +locale_ru=\u0420\u0443\u0441\u0441\u043A\u0438\u0439 + +backToApplication=« Tilbake til applikasjonen +missingParameterMessage=Manglende parameter\: {0} +clientNotFoundMessage=Klient ikke funnet. +clientDisabledMessage=Klient deaktivert. +invalidParameterMessage=Ugyldig parameter\: {0} +alreadyLoggedIn=Du er allerede innlogget. + +p3pPolicy=CP="Dette er ikke en P3P policy!" diff --git a/themes/src/main/resources/theme/base/login/theme.properties b/themes/src/main/resources/theme/base/login/theme.properties index 2f853e89da..2cf724db33 100644 --- a/themes/src/main/resources/theme/base/login/theme.properties +++ b/themes/src/main/resources/theme/base/login/theme.properties @@ -1 +1 @@ -locales=ca,de,en,es,fr,it,ja,pt-BR,ru \ No newline at end of file +locales=ca,de,en,es,fr,it,ja,lt,no,pt-BR,ru \ No newline at end of file diff --git a/wildfly/server-subsystem/src/main/config/default-server-subsys-config.properties b/wildfly/server-subsystem/src/main/config/default-server-subsys-config.properties index 2355ab0e7e..e29d1bd303 100644 --- a/wildfly/server-subsystem/src/main/config/default-server-subsys-config.properties +++ b/wildfly/server-subsystem/src/main/config/default-server-subsys-config.properties @@ -30,6 +30,9 @@ keycloak.server.subsys.default.config=\ \ jpa\ \ + \ + jpa\ + \ \ \ \ diff --git a/wildfly/server-subsystem/src/main/resources/subsystem-templates/keycloak-datasources.xml b/wildfly/server-subsystem/src/main/resources/subsystem-templates/keycloak-datasources.xml index 326fa7f26f..c823e4f3a3 100755 --- a/wildfly/server-subsystem/src/main/resources/subsystem-templates/keycloak-datasources.xml +++ b/wildfly/server-subsystem/src/main/resources/subsystem-templates/keycloak-datasources.xml @@ -29,7 +29,7 @@ sa - + h2
  • {{:: 'name' | translate}} {{:: 'provider' | translate}} {{:: 'enabled' | translate}}{{:: 'actions' | translate}}
    {{identityProvider.alias}} diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/role-mappings.html b/themes/src/main/resources/theme/base/admin/resources/partials/role-mappings.html index fd1a0fadca..9970be4801 100755 --- a/themes/src/main/resources/theme/base/admin/resources/partials/role-mappings.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/role-mappings.html @@ -84,7 +84,8 @@
    - + + {{:: 'user.effective-roles-client.tooltip' | translate}}
    {{serverInfo.systemInfo.version}}
    {{:: 'server-profile' | translate}}{{serverInfo.profileInfo.name}}
    {{:: 'server-time' | translate}} {{serverInfo.systemInfo.serverTime}}