diff --git a/adapters/oidc/spring-security/src/main/java/org/keycloak/adapters/springsecurity/KeycloakConfiguration.java b/adapters/oidc/spring-security/src/main/java/org/keycloak/adapters/springsecurity/KeycloakConfiguration.java
new file mode 100644
index 0000000000..f434b97baa
--- /dev/null
+++ b/adapters/oidc/spring-security/src/main/java/org/keycloak/adapters/springsecurity/KeycloakConfiguration.java
@@ -0,0 +1,25 @@
+package org.keycloak.adapters.springsecurity;
+
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+/**
+ * Add this annotation to a class that extends {@code KeycloakWebSecurityConfigurerAdapter} to provide
+ * a keycloak based Spring security configuration.
+ *
+ * @author Hendrik Ebbers
+ */
+@Retention(value = RUNTIME)
+@Target(value = { TYPE })
+@Configuration
+@ComponentScan(basePackageClasses = KeycloakSecurityComponents.class)
+@EnableWebSecurity
+public @interface KeycloakConfiguration {
+}
diff --git a/adapters/oidc/spring-security/src/main/java/org/keycloak/adapters/springsecurity/filter/KeycloakAuthenticationProcessingFilter.java b/adapters/oidc/spring-security/src/main/java/org/keycloak/adapters/springsecurity/filter/KeycloakAuthenticationProcessingFilter.java
index fabf30e62d..7e235ae520 100644
--- a/adapters/oidc/spring-security/src/main/java/org/keycloak/adapters/springsecurity/filter/KeycloakAuthenticationProcessingFilter.java
+++ b/adapters/oidc/spring-security/src/main/java/org/keycloak/adapters/springsecurity/filter/KeycloakAuthenticationProcessingFilter.java
@@ -61,15 +61,19 @@ import org.springframework.util.Assert;
* @version $Revision: 1 $
*/
public class KeycloakAuthenticationProcessingFilter extends AbstractAuthenticationProcessingFilter implements ApplicationContextAware {
-
+
+ public static final String DEFAULT_LOGIN_URL = "/sso/login";
public static final String AUTHORIZATION_HEADER = "Authorization";
public static final String SCHEME_BEARER = "bearer ";
public static final String SCHEME_BASIC = "basic ";
+
/**
- * Request matcher that matches all requests.
+ * Request matcher that matches requests to the {@link KeycloakAuthenticationEntryPoint#DEFAULT_LOGIN_URI default login URI}
+ * and any request with a Authorization header.
*/
- private static RequestMatcher DEFAULT_REQUEST_MATCHER = new AntPathRequestMatcher("/**");
+ public static final RequestMatcher DEFAULT_REQUEST_MATCHER =
+ new OrRequestMatcher(new AntPathRequestMatcher(DEFAULT_LOGIN_URL), new RequestHeaderRequestMatcher(AUTHORIZATION_HEADER));
private static final Logger log = LoggerFactory.getLogger(KeycloakAuthenticationProcessingFilter.class);
@@ -107,7 +111,7 @@ public class KeycloakAuthenticationProcessingFilter extends AbstractAuthenticati
*
*/
public KeycloakAuthenticationProcessingFilter(AuthenticationManager authenticationManager, RequestMatcher
- requiresAuthenticationRequestMatcher) {
+ requiresAuthenticationRequestMatcher) {
super(requiresAuthenticationRequestMatcher);
Assert.notNull(authenticationManager, "authenticationManager cannot be null");
this.authenticationManager = authenticationManager;
@@ -138,20 +142,27 @@ public class KeycloakAuthenticationProcessingFilter extends AbstractAuthenticati
log.debug("Auth outcome: {}", result);
if (AuthOutcome.FAILED.equals(result)) {
- AuthChallenge challenge = authenticator.getChallenge();
+ AuthChallenge challenge = authenticator.getChallenge();
if (challenge != null) {
challenge.challenge(facade);
}
throw new KeycloakAuthenticationException("Invalid authorization header, see WWW-Authenticate header for details");
}
+
if (AuthOutcome.NOT_ATTEMPTED.equals(result)) {
- AuthChallenge challenge = authenticator.getChallenge();
+ AuthChallenge challenge = authenticator.getChallenge();
if (challenge != null) {
challenge.challenge(facade);
}
- throw new KeycloakAuthenticationException("Authorization header not found, see WWW-Authenticate header");
+ if (deployment.isBearerOnly()) {
+ // no redirection in this mode, throwing exception for the spring handler
+ throw new KeycloakAuthenticationException("Authorization header not found, see WWW-Authenticate header");
+ } else {
+ // let continue if challenged, it may redirect
+ return null;
+ }
}
-
+
else if (AuthOutcome.AUTHENTICATED.equals(result)) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
Assert.notNull(authentication, "Authentication SecurityContextHolder was null");
@@ -193,7 +204,7 @@ public class KeycloakAuthenticationProcessingFilter extends AbstractAuthenticati
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
- Authentication authResult) throws IOException, ServletException {
+ Authentication authResult) throws IOException, ServletException {
if (!(this.isBearerTokenRequest(request) || this.isBasicAuthRequest(request))) {
super.successfulAuthentication(request, response, chain, authResult);
@@ -220,10 +231,10 @@ public class KeycloakAuthenticationProcessingFilter extends AbstractAuthenticati
}
@Override
- protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
- AuthenticationException failed) throws IOException, ServletException {
- super.unsuccessfulAuthentication(request, response, failed);
- }
+ protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
+ AuthenticationException failed) throws IOException, ServletException {
+ super.unsuccessfulAuthentication(request, response, failed);
+ }
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
@@ -259,4 +270,4 @@ public class KeycloakAuthenticationProcessingFilter extends AbstractAuthenticati
public final void setContinueChainBeforeSuccessfulAuthentication(boolean continueChainBeforeSuccessfulAuthentication) {
throw new UnsupportedOperationException("This filter does not support explicitly setting a continue chain before success policy");
}
-}
+}
\ No newline at end of file
diff --git a/adapters/oidc/spring-security/src/test/java/org/keycloak/adapters/springsecurity/filter/KeycloakAuthenticationProcessingFilterTest.java b/adapters/oidc/spring-security/src/test/java/org/keycloak/adapters/springsecurity/filter/KeycloakAuthenticationProcessingFilterTest.java
index 2414c38f24..a6a378a34c 100755
--- a/adapters/oidc/spring-security/src/test/java/org/keycloak/adapters/springsecurity/filter/KeycloakAuthenticationProcessingFilterTest.java
+++ b/adapters/oidc/spring-security/src/test/java/org/keycloak/adapters/springsecurity/filter/KeycloakAuthenticationProcessingFilterTest.java
@@ -159,12 +159,10 @@ public class KeycloakAuthenticationProcessingFilterTest {
when(keycloakDeployment.getStateCookieName()).thenReturn("kc-cookie");
when(keycloakDeployment.getSslRequired()).thenReturn(SslRequired.NONE);
when(keycloakDeployment.isBearerOnly()).thenReturn(Boolean.FALSE);
- try {
- filter.attemptAuthentication(request, response);
- } catch (KeycloakAuthenticationException e) {
- verify(response).setStatus(302);
- verify(response).setHeader(eq("Location"), startsWith("http://localhost:8080/auth"));
- }
+
+ filter.attemptAuthentication(request, response);
+ verify(response).setStatus(302);
+ verify(response).setHeader(eq("Location"), startsWith("http://localhost:8080/auth"));
}
@Test(expected = KeycloakAuthenticationException.class)
@@ -173,6 +171,13 @@ public class KeycloakAuthenticationProcessingFilterTest {
filter.attemptAuthentication(request, response);
}
+ @Test(expected = KeycloakAuthenticationException.class)
+ public void testAttemptAuthenticationWithInvalidTokenBearerOnly() throws Exception {
+ when(keycloakDeployment.isBearerOnly()).thenReturn(Boolean.TRUE);
+ request.addHeader("Authorization", "Bearer xxx");
+ filter.attemptAuthentication(request, response);
+ }
+
@Test
public void testSuccessfulAuthenticationInteractive() throws Exception {
Authentication authentication = new KeycloakAuthenticationToken(keycloakAccount, authorities);
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/DefaultSamlDeployment.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/DefaultSamlDeployment.java
index d92884fd5d..9a6e2880ec 100755
--- a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/DefaultSamlDeployment.java
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/DefaultSamlDeployment.java
@@ -294,6 +294,7 @@ public class DefaultSamlDeployment implements SamlDeployment {
private String logoutPage;
private SignatureAlgorithm signatureAlgorithm;
private String signatureCanonicalizationMethod;
+ private boolean autodetectBearerOnly;
@Override
public boolean turnOffChangeSessionIdOnLogin() {
@@ -439,4 +440,13 @@ public class DefaultSamlDeployment implements SamlDeployment {
public void setSignatureAlgorithm(SignatureAlgorithm signatureAlgorithm) {
this.signatureAlgorithm = signatureAlgorithm;
}
+
+ @Override
+ public boolean isAutodetectBearerOnly() {
+ return autodetectBearerOnly;
+ }
+
+ public void setAutodetectBearerOnly(boolean autodetectBearerOnly) {
+ this.autodetectBearerOnly = autodetectBearerOnly;
+ }
}
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/SamlDeployment.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/SamlDeployment.java
index a9df7e9461..0b7ac40ecf 100755
--- a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/SamlDeployment.java
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/SamlDeployment.java
@@ -168,6 +168,6 @@ public interface SamlDeployment {
}
PrincipalNamePolicy getPrincipalNamePolicy();
String getPrincipalAttributeName();
-
+ boolean isAutodetectBearerOnly();
}
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/SP.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/SP.java
index 475a0f491c..071b170d2f 100755
--- a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/SP.java
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/SP.java
@@ -58,6 +58,7 @@ public class SP implements Serializable {
private PrincipalNameMapping principalNameMapping;
private Set roleAttributes;
private IDP idp;
+ private boolean autodetectBearerOnly;
public String getEntityID() {
return entityID;
@@ -147,4 +148,11 @@ public class SP implements Serializable {
this.logoutPage = logoutPage;
}
+ public boolean isAutodetectBearerOnly() {
+ return autodetectBearerOnly;
+ }
+
+ public void setAutodetectBearerOnly(boolean autodetectBearerOnly) {
+ this.autodetectBearerOnly = autodetectBearerOnly;
+ }
}
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/ConfigXmlConstants.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/ConfigXmlConstants.java
index 256bfa2a97..ecde04a3fc 100755
--- a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/ConfigXmlConstants.java
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/ConfigXmlConstants.java
@@ -33,6 +33,7 @@ public class ConfigXmlConstants {
public static final String SIGNATURE_ALGORITHM_ATTR = "signatureAlgorithm";
public static final String SIGNATURE_CANONICALIZATION_METHOD_ATTR = "signatureCanonicalizationMethod";
public static final String LOGOUT_PAGE_ATTR = "logoutPage";
+ public static final String AUTODETECT_BEARER_ONLY_ATTR = "autodetectBearerOnly";
public static final String KEYS_ELEMENT = "Keys";
public static final String KEY_ELEMENT = "Key";
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/DeploymentBuilder.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/DeploymentBuilder.java
index b5f8abad22..92254a2483 100755
--- a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/DeploymentBuilder.java
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/DeploymentBuilder.java
@@ -68,6 +68,7 @@ public class DeploymentBuilder {
deployment.setNameIDPolicyFormat(sp.getNameIDPolicyFormat());
deployment.setLogoutPage(sp.getLogoutPage());
deployment.setSignatureCanonicalizationMethod(sp.getIdp().getSignatureCanonicalizationMethod());
+ deployment.setAutodetectBearerOnly(sp.isAutodetectBearerOnly());
deployment.setSignatureAlgorithm(SignatureAlgorithm.RSA_SHA256);
if (sp.getIdp().getSignatureAlgorithm() != null) {
deployment.setSignatureAlgorithm(SignatureAlgorithm.valueOf(sp.getIdp().getSignatureAlgorithm()));
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/SPXmlParser.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/SPXmlParser.java
index be6d682705..57d5f58f62 100755
--- a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/SPXmlParser.java
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/SPXmlParser.java
@@ -89,6 +89,7 @@ public class SPXmlParser extends AbstractParser {
sp.setNameIDPolicyFormat(getAttributeValue(startElement, ConfigXmlConstants.NAME_ID_POLICY_FORMAT_ATTR));
sp.setForceAuthentication(getBooleanAttributeValue(startElement, ConfigXmlConstants.FORCE_AUTHENTICATION_ATTR));
sp.setIsPassive(getBooleanAttributeValue(startElement, ConfigXmlConstants.IS_PASSIVE_ATTR));
+ sp.setAutodetectBearerOnly(getBooleanAttributeValue(startElement, ConfigXmlConstants.AUTODETECT_BEARER_ONLY_ATTR));
sp.setTurnOffChangeSessionIdOnLogin(getBooleanAttributeValue(startElement, ConfigXmlConstants.TURN_OFF_CHANGE_SESSSION_ID_ON_LOGIN_ATTR));
while (xmlEventReader.hasNext()) {
XMLEvent xmlEvent = StaxParserUtil.peek(xmlEventReader);
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/profile/AbstractSamlAuthenticationHandler.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/profile/AbstractSamlAuthenticationHandler.java
index 5721b038a9..08ce4a988b 100644
--- a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/profile/AbstractSamlAuthenticationHandler.java
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/profile/AbstractSamlAuthenticationHandler.java
@@ -364,26 +364,26 @@ public abstract class AbstractSamlAuthenticationHandler implements SamlAuthentic
if (deployment.getIDP().getSingleSignOnService().validateAssertionSignature()) {
try {
- validateSamlSignature(new SAMLDocumentHolder(buildAssertionDocument(responseHolder, assertion)), postBinding, GeneralConstants.SAML_RESPONSE_KEY);
- } catch (VerificationException e) {
- log.error("Failed to verify saml assertion signature", e);
+ if (!AssertionUtil.isSignatureValid(getAssertionFromResponse(responseHolder), deployment.getIDP().getSignatureValidationKeyLocator())) {
+ log.error("Failed to verify saml assertion signature");
- challenge = new AuthChallenge() {
+ challenge = new AuthChallenge() {
- @Override
- public boolean challenge(HttpFacade exchange) {
- SamlAuthenticationError error = new SamlAuthenticationError(SamlAuthenticationError.Reason.INVALID_SIGNATURE, responseType);
- exchange.getRequest().setError(error);
- exchange.getResponse().sendError(403);
- return true;
- }
+ @Override
+ public boolean challenge(HttpFacade exchange) {
+ SamlAuthenticationError error = new SamlAuthenticationError(SamlAuthenticationError.Reason.INVALID_SIGNATURE, responseType);
+ exchange.getRequest().setError(error);
+ exchange.getResponse().sendError(403);
+ return true;
+ }
- @Override
- public int getResponseCode() {
- return 403;
- }
- };
- return AuthOutcome.FAILED;
+ @Override
+ public int getResponseCode() {
+ return 403;
+ }
+ };
+ return AuthOutcome.FAILED;
+ }
} catch (Exception e) {
log.error("Error processing validation of SAML assertion: " + e.getMessage());
challenge = new AuthChallenge() {
@@ -504,19 +504,16 @@ public abstract class AbstractSamlAuthenticationHandler implements SamlAuthentic
&& Objects.equals(responseType.getStatus().getStatusCode().getValue().toString(), JBossSAMLURIConstants.STATUS_SUCCESS.get());
}
- private Document buildAssertionDocument(final SAMLDocumentHolder responseHolder, AssertionType assertion) throws ConfigurationException, ProcessingException {
- Element encryptedAssertion = org.keycloak.saml.common.util.DocumentUtil.getElement(responseHolder.getSamlDocument(), new QName(JBossSAMLConstants.ENCRYPTED_ASSERTION.get()));
+ private Element getAssertionFromResponse(final SAMLDocumentHolder responseHolder) throws ConfigurationException, ProcessingException {
+ Element encryptedAssertion = DocumentUtil.getElement(responseHolder.getSamlDocument(), new QName(JBossSAMLConstants.ENCRYPTED_ASSERTION.get()));
if (encryptedAssertion != null) {
// encrypted assertion.
// We'll need to decrypt it first.
Document encryptedAssertionDocument = DocumentUtil.createDocument();
encryptedAssertionDocument.appendChild(encryptedAssertionDocument.importNode(encryptedAssertion, true));
- Element assertionElement = XMLEncryptionUtil.decryptElementInDocument(encryptedAssertionDocument, deployment.getDecryptionKey());
- Document assertionDocument = DocumentUtil.createDocument();
- assertionDocument.appendChild(assertionDocument.importNode(assertionElement, true));
- return assertionDocument;
+ return XMLEncryptionUtil.decryptElementInDocument(encryptedAssertionDocument, deployment.getDecryptionKey());
}
- return AssertionUtil.asDocument(assertion);
+ return DocumentUtil.getElement(responseHolder.getSamlDocument(), new QName(JBossSAMLConstants.ASSERTION.get()));
}
private String getAttributeValue(Object attrValue) {
@@ -568,9 +565,15 @@ public abstract class AbstractSamlAuthenticationHandler implements SamlAuthentic
return new AbstractInitiateLogin(deployment, sessionStore) {
@Override
protected void sendAuthnRequest(HttpFacade httpFacade, SAML2AuthnRequestBuilder authnRequestBuilder, BaseSAML2BindingBuilder binding) throws ProcessingException, ConfigurationException, IOException {
- Document document = authnRequestBuilder.toDocument();
- SamlDeployment.Binding samlBinding = deployment.getIDP().getSingleSignOnService().getRequestBinding();
- SamlUtil.sendSaml(true, httpFacade, deployment.getIDP().getSingleSignOnService().getRequestBindingUrl(), binding, document, samlBinding);
+ if (isAutodetectedBearerOnly(httpFacade.getRequest())) {
+ httpFacade.getResponse().setStatus(401);
+ httpFacade.getResponse().end();
+ }
+ else {
+ Document document = authnRequestBuilder.toDocument();
+ SamlDeployment.Binding samlBinding = deployment.getIDP().getSingleSignOnService().getRequestBinding();
+ SamlUtil.sendSaml(true, httpFacade, deployment.getIDP().getSingleSignOnService().getRequestBindingUrl(), binding, document, samlBinding);
+ }
}
};
}
@@ -693,4 +696,34 @@ public abstract class AbstractSamlAuthenticationHandler implements SamlAuthentic
return signature.verify(decodedSignature);
}
+
+ protected boolean isAutodetectedBearerOnly(HttpFacade.Request request) {
+ if (!deployment.isAutodetectBearerOnly()) return false;
+
+ String headerValue = facade.getRequest().getHeader(GeneralConstants.HTTP_HEADER_X_REQUESTED_WITH);
+ if (headerValue != null && headerValue.equalsIgnoreCase("XMLHttpRequest")) {
+ return true;
+ }
+
+ headerValue = facade.getRequest().getHeader("Faces-Request");
+ if (headerValue != null && headerValue.startsWith("partial/")) {
+ return true;
+ }
+
+ headerValue = facade.getRequest().getHeader("SOAPAction");
+ if (headerValue != null) {
+ return true;
+ }
+
+ List accepts = facade.getRequest().getHeaders("Accept");
+ if (accepts == null) accepts = Collections.emptyList();
+
+ for (String accept : accepts) {
+ if (accept.contains("text/html") || accept.contains("text/*") || accept.contains("*/*")) {
+ return false;
+ }
+ }
+
+ return true;
+ }
}
diff --git a/adapters/saml/core/src/main/resources/schema/keycloak_saml_adapter_1_9.xsd b/adapters/saml/core/src/main/resources/schema/keycloak_saml_adapter_1_9.xsd
new file mode 100644
index 0000000000..cf9c05c4a7
--- /dev/null
+++ b/adapters/saml/core/src/main/resources/schema/keycloak_saml_adapter_1_9.xsd
@@ -0,0 +1,461 @@
+
+
+
+
+
+
+
+
+
+ Keycloak SAML Adapter configuration file.
+
+
+
+
+ Describes SAML service provider configuration.
+
+
+
+
+
+
+
+
+
+
+ List of service provider encryption and validation keys.
+
+ If the IDP requires that the client application (SP) sign all of its requests and/or if the IDP will encrypt assertions, you must define the keys used to do this. For client signed documents you must define both the private and public key or certificate that will be used to sign documents. For encryption, you only have to define the private key that will be used to decrypt.
+
+
+
+
+ When creating a Java Principal object that you obtain from methods like HttpServletRequest.getUserPrincipal(), you can define what name that is returned by the Principal.getName() method.
+
+
+
+
+ Defines what SAML attributes within the assertion received from the user should be used as role identifiers within the Java EE Security Context for the user.
+ By default Role attribute values are converted to Java EE roles. Some IDPs send roles via a member or memberOf attribute assertion. You can define one or more Attribute elements to specify which SAML attributes must be converted into roles.
+
+
+
+
+ Describes configuration of SAML identity provider for this service provider.
+
+
+
+
+
+ This is the identifier for this client. The IDP needs this value to determine who the client is that is communicating with it.
+
+
+
+
+ SSL policy the adapter will enforce.
+
+
+
+
+ SAML clients can request a specific NameID Subject format. Fill in this value if you want a specific format. It must be a standard SAML format identifier, i.e. urn:oasis:names:tc:SAML:2.0:nameid-format:transient. By default, no special format is requested.
+
+
+
+
+ URL of the logout page.
+
+
+
+
+ SAML clients can request that a user is re-authenticated even if they are already logged in at the IDP. Default value is false.
+
+
+
+
+ SAML clients can request that a user is never asked to authenticate even if they are not logged in at the IDP. Set this to true if you want this. Do not use together with forceAuthentication as they are opposite. Default value is false.
+
+
+
+
+ The session id is changed by default on a successful login on some platforms to plug a security attack vector. Change this to true to disable this. It is recommended you do not turn it off. Default value is false.
+
+
+
+
+ This should be set to true if your application serves both a web application and web services (e.g. SOAP or REST). It allows you to redirect unauthenticated users of the web application to the Keycloak login page, but send an HTTP 401 status code to unauthenticated SOAP or REST clients instead as they would not understand a redirect to the login page. Keycloak auto-detects SOAP or REST clients based on typical headers like X-Requested-With, SOAPAction or Accept. The default value is false.
+
+
+
+
+
+
+
+
+ Describes a single key used for signing or encryption.
+
+
+
+
+
+
+
+
+ Java keystore to load keys and certificates from.
+
+
+
+
+ Private key (PEM format)
+
+
+
+
+ Public key (PEM format)
+
+
+
+
+ Certificate key (PEM format)
+
+
+
+
+
+ Flag defining whether the key should be used for signing.
+
+
+
+
+ Flag defining whether the key should be used for encryption
+
+
+
+
+
+
+
+ Private key declaration
+
+
+
+
+ Certificate declaration
+
+
+
+
+
+ File path to the key store.
+
+
+
+
+ WAR resource path to the key store. This is a path used in method call to ServletContext.getResourceAsStream().
+
+
+
+
+ The password of the key store.
+
+
+
+
+
+
+ Alias that points to the key or cert within the keystore.
+
+
+
+
+ Keystores require an additional password to access private keys. In the PrivateKey element you must define this password within a password attribute.
+
+
+
+
+
+
+ Alias that points to the key or cert within the keystore.
+
+
+
+
+
+
+ Policy used to populate value of Java Principal object obtained from methods like HttpServletRequest.getUserPrincipal().
+
+
+
+
+ Name of the SAML assertion attribute to use within.
+
+
+
+
+
+
+
+ This policy just uses whatever the SAML subject value is. This is the default setting
+
+
+
+
+ This will pull the value from one of the attributes declared in the SAML assertion received from the server. You'll need to specify the name of the SAML assertion attribute to use within the attribute XML attribute.
+
+
+
+
+
+
+
+
+ All requests must come in via HTTPS.
+
+
+
+
+ Only non-private IP addresses must come over the wire via HTTPS.
+
+
+
+
+ no requests are required to come over via HTTPS.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Specifies SAML attribute to be converted into roles.
+
+
+
+
+
+
+
+ Specifies name of the SAML attribute to be converted into roles.
+
+
+
+
+
+
+
+ Configuration of the login SAML endpoint of the IDP.
+
+
+
+
+ Configuration of the logout SAML endpoint of the IDP
+
+
+
+
+ The Keys sub element of IDP is only used to define the certificate or public key to use to verify documents signed by the IDP.
+
+
+
+
+ Configuration of HTTP client used for automatic obtaining of certificates containing public keys for IDP signature verification via SAML descriptor of the IDP.
+
+
+
+
+
+ issuer ID of the IDP.
+
+
+
+
+ If set to true, the client adapter will sign every document it sends to the IDP. Also, the client will expect that the IDP will be signing any documents sent to it. This switch sets the default for all request and response types.
+
+
+
+
+ Signature algorithm that the IDP expects signed documents to use. Defaults to RSA_SHA256
+
+
+
+
+ This is the signature canonicalization method that the IDP expects signed documents to use. The default value is https://www.w3.org/2001/10/xml-exc-c14n# and should be good for most IDPs.
+
+
+
+
+
+
+
+
+
+
+
+ Should the client sign authn requests? Defaults to whatever the IDP signaturesRequired element value is.
+
+
+
+
+ Should the client expect the IDP to sign the assertion response document sent back from an auhtn request? Defaults to whatever the IDP signaturesRequired element value is.
+
+
+
+
+ Should the client expect the IDP to sign the individual assertions sent back from an auhtn request? Defaults to whatever the IDP signaturesRequired element value is.
+
+
+
+
+ SAML binding type used for communicating with the IDP. The default value is POST, but you can set it to REDIRECT as well.
+
+
+
+
+ SAML allows the client to request what binding type it wants authn responses to use. This value maps to ProtocolBinding attribute in SAML AuthnRequest. The default is that the client will not request a specific binding type for responses.
+
+
+
+
+ This is the URL for the IDP login service that the client will send requests to.
+
+
+
+
+ URL of the assertion consumer service (ACS) where the IDP login service should send responses to. By default it is unset, relying on the IdP settings. When set, it must end in "/saml". This property is typically accompanied by the responseBinding attribute.
+
+
+
+
+
+
+
+ Should the client sign authn requests? Defaults to whatever the IDP signaturesRequired element value is.
+
+
+
+
+ Should the client sign logout responses it sends to the IDP requests? Defaults to whatever the IDP signaturesRequired element value is.
+
+
+
+
+ Should the client expect signed logout request documents from the IDP? Defaults to whatever the IDP signaturesRequired element value is.
+
+
+
+
+ Should the client expect signed logout response documents from the IDP? Defaults to whatever the IDP signaturesRequired element value is.
+
+
+
+
+ This is the SAML binding type used for communicating SAML requests to the IDP. The default value is POST.
+
+
+
+
+ This is the SAML binding type used for communicating SAML responses to the IDP. The default value is POST.
+
+
+
+
+ This is the URL for the IDP's logout service when using the POST binding. This setting is REQUIRED if using the POST binding.
+
+
+
+
+ This is the URL for the IDP's logout service when using the REDIRECT binding. This setting is REQUIRED if using the REDIRECT binding.
+
+
+
+
+
+
+
+ If the the IDP server requires HTTPS and this config option is set to true the IDP's certificate
+ is validated via the truststore, but host name validation is not done. This setting should only be used during
+ development and never in production as it will partly disable verification of SSL certificates.
+ This seting may be useful in test environments. The default value is false.
+
+
+
+
+ This is the file path to a keystore file. This keystore contains client certificate
+ for two-way SSL when the adapter makes HTTPS requests to the IDP server.
+
+
+
+
+ Password for the client keystore and for the client's key.
+
+
+
+
+ Defines number of pooled connections.
+
+
+
+
+ If the the IDP server requires HTTPS and this config option is set to true you do not have to specify a truststore.
+ This setting should only be used during development and never in production as it will disable verification of SSL certificates.
+ The default value is false.
+
+
+
+
+ URL to HTTP proxy to use for HTTP connections.
+
+
+
+
+ The value is the file path to a keystore file. If you prefix the path with classpath:,
+ then the truststore will be obtained from the deployment's classpath instead. Used for outgoing
+ HTTPS communications to the IDP server. Client making HTTPS requests need
+ a way to verify the host of the server they are talking to. This is what the trustore does.
+ The keystore contains one or more trusted host certificates or certificate authorities.
+ You can create this truststore by extracting the public certificate of the IDP's SSL keystore.
+
+
+
+
+
+ Password for the truststore keystore.
+
+
+
+
+
diff --git a/adapters/saml/core/src/test/java/org/keycloak/adapters/saml/config/parsers/KeycloakSamlAdapterXMLParserTest.java b/adapters/saml/core/src/test/java/org/keycloak/adapters/saml/config/parsers/KeycloakSamlAdapterXMLParserTest.java
index b64f181dcc..88a96cb142 100755
--- a/adapters/saml/core/src/test/java/org/keycloak/adapters/saml/config/parsers/KeycloakSamlAdapterXMLParserTest.java
+++ b/adapters/saml/core/src/test/java/org/keycloak/adapters/saml/config/parsers/KeycloakSamlAdapterXMLParserTest.java
@@ -37,7 +37,7 @@ import org.keycloak.saml.common.exceptions.ParsingException;
*/
public class KeycloakSamlAdapterXMLParserTest {
- private static final String CURRENT_XSD_LOCATION = "/schema/keycloak_saml_adapter_1_7.xsd";
+ private static final String CURRENT_XSD_LOCATION = "/schema/keycloak_saml_adapter_1_9.xsd";
@Rule
public ExpectedException expectedException = ExpectedException.none();
@@ -91,6 +91,7 @@ public class KeycloakSamlAdapterXMLParserTest {
assertEquals("format", sp.getNameIDPolicyFormat());
assertTrue(sp.isForceAuthentication());
assertTrue(sp.isIsPassive());
+ assertFalse(sp.isAutodetectBearerOnly());
assertEquals(2, sp.getKeys().size());
Key signing = sp.getKeys().get(0);
assertTrue(signing.isSigning());
diff --git a/adapters/saml/core/src/test/resources/org/keycloak/adapters/saml/config/parsers/keycloak-saml.xml b/adapters/saml/core/src/test/resources/org/keycloak/adapters/saml/config/parsers/keycloak-saml.xml
index 193525a894..31f62e5856 100755
--- a/adapters/saml/core/src/test/resources/org/keycloak/adapters/saml/config/parsers/keycloak-saml.xml
+++ b/adapters/saml/core/src/test/resources/org/keycloak/adapters/saml/config/parsers/keycloak-saml.xml
@@ -17,7 +17,7 @@
+ xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_9.xsd">
-
\ No newline at end of file
+
diff --git a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/group/GroupPolicyProvider.java b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/group/GroupPolicyProvider.java
new file mode 100644
index 0000000000..2ae1bdc4b3
--- /dev/null
+++ b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/group/GroupPolicyProvider.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2017 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.authorization.policy.provider.group;
+
+import static org.keycloak.models.utils.ModelToRepresentation.buildGroupPath;
+
+import java.util.function.Function;
+
+import org.keycloak.authorization.attribute.Attributes;
+import org.keycloak.authorization.model.Policy;
+import org.keycloak.authorization.policy.evaluation.Evaluation;
+import org.keycloak.authorization.policy.provider.PolicyProvider;
+import org.keycloak.models.GroupModel;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.utils.ModelToRepresentation;
+import org.keycloak.representations.idm.authorization.GroupPolicyRepresentation;
+
+/**
+ * @author Pedro Igor
+ */
+public class GroupPolicyProvider implements PolicyProvider {
+
+ private final Function representationFunction;
+
+ public GroupPolicyProvider(Function representationFunction) {
+ this.representationFunction = representationFunction;
+ }
+
+ @Override
+ public void evaluate(Evaluation evaluation) {
+ GroupPolicyRepresentation policy = representationFunction.apply(evaluation.getPolicy());
+ RealmModel realm = evaluation.getAuthorizationProvider().getRealm();
+ Attributes.Entry groupsClaim = evaluation.getContext().getIdentity().getAttributes().getValue(policy.getGroupsClaim());
+
+ if (groupsClaim == null || groupsClaim.isEmpty()) {
+ return;
+ }
+
+ for (GroupPolicyRepresentation.GroupDefinition definition : policy.getGroups()) {
+ GroupModel allowedGroup = realm.getGroupById(definition.getId());
+
+ for (int i = 0; i < groupsClaim.size(); i++) {
+ String group = groupsClaim.asString(i);
+
+ if (group.indexOf('/') != -1) {
+ String allowedGroupPath = buildGroupPath(allowedGroup);
+ if (group.equals(allowedGroupPath) || (definition.isExtendChildren() && group.startsWith(allowedGroupPath))) {
+ evaluation.grant();
+ return;
+ }
+ }
+
+ // in case the group from the claim does not represent a path, we just check an exact name match
+ if (group.equals(allowedGroup.getName())) {
+ evaluation.grant();
+ return;
+ }
+ }
+ }
+ }
+
+ @Override
+ public void close() {
+
+ }
+}
\ No newline at end of file
diff --git a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/group/GroupPolicyProviderFactory.java b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/group/GroupPolicyProviderFactory.java
new file mode 100644
index 0000000000..f55844978d
--- /dev/null
+++ b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/group/GroupPolicyProviderFactory.java
@@ -0,0 +1,214 @@
+/*
+ * Copyright 2017 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.authorization.policy.provider.group;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.keycloak.Config;
+import org.keycloak.authorization.AuthorizationProvider;
+import org.keycloak.authorization.model.Policy;
+import org.keycloak.authorization.policy.provider.PolicyProvider;
+import org.keycloak.authorization.policy.provider.PolicyProviderFactory;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.GroupModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.models.RoleModel;
+import org.keycloak.models.utils.ModelToRepresentation;
+import org.keycloak.representations.idm.authorization.GroupPolicyRepresentation;
+import org.keycloak.representations.idm.authorization.PolicyRepresentation;
+import org.keycloak.representations.idm.authorization.RolePolicyRepresentation;
+import org.keycloak.util.JsonSerialization;
+
+/**
+ * @author Pedro Igor
+ */
+public class GroupPolicyProviderFactory implements PolicyProviderFactory {
+
+ private GroupPolicyProvider provider = new GroupPolicyProvider(policy -> toRepresentation(policy, new GroupPolicyRepresentation()));
+
+ @Override
+ public String getId() {
+ return "group";
+ }
+
+ @Override
+ public String getName() {
+ return "Group";
+ }
+
+ @Override
+ public String getGroup() {
+ return "Identity Based";
+ }
+
+ @Override
+ public PolicyProvider create(AuthorizationProvider authorization) {
+ return provider;
+ }
+
+ @Override
+ public PolicyProvider create(KeycloakSession session) {
+ return provider;
+ }
+
+ @Override
+ public GroupPolicyRepresentation toRepresentation(Policy policy, GroupPolicyRepresentation representation) {
+ representation.setGroupsClaim(policy.getConfig().get("groupsClaim"));
+ try {
+ representation.setGroups(getGroupsDefinition(policy.getConfig()));
+ } catch (IOException cause) {
+ throw new RuntimeException("Failed to deserialize groups", cause);
+ }
+ return representation;
+ }
+
+ @Override
+ public Class getRepresentationType() {
+ return GroupPolicyRepresentation.class;
+ }
+
+ @Override
+ public void onCreate(Policy policy, GroupPolicyRepresentation representation, AuthorizationProvider authorization) {
+ updatePolicy(policy, representation.getGroupsClaim(), representation.getGroups(), authorization);
+ }
+
+ @Override
+ public void onUpdate(Policy policy, GroupPolicyRepresentation representation, AuthorizationProvider authorization) {
+ updatePolicy(policy, representation.getGroupsClaim(), representation.getGroups(), authorization);
+ }
+
+ @Override
+ public void onImport(Policy policy, PolicyRepresentation representation, AuthorizationProvider authorization) {
+ try {
+ updatePolicy(policy, representation.getConfig().get("groupsClaim"), getGroupsDefinition(representation.getConfig()), authorization);
+ } catch (IOException cause) {
+ throw new RuntimeException("Failed to deserialize groups", cause);
+ }
+ }
+
+ @Override
+ public void onExport(Policy policy, PolicyRepresentation representation, AuthorizationProvider authorizationProvider) {
+ Map config = new HashMap<>();
+ GroupPolicyRepresentation groupPolicy = toRepresentation(policy, new GroupPolicyRepresentation());
+ Set groups = groupPolicy.getGroups();
+
+ for (GroupPolicyRepresentation.GroupDefinition definition: groups) {
+ GroupModel group = authorizationProvider.getRealm().getGroupById(definition.getId());
+ definition.setId(null);
+ definition.setPath(ModelToRepresentation.buildGroupPath(group));
+ }
+
+ try {
+ config.put("groupsClaim", groupPolicy.getGroupsClaim());
+ config.put("groups", JsonSerialization.writeValueAsString(groups));
+ } catch (IOException cause) {
+ throw new RuntimeException("Failed to export group policy [" + policy.getName() + "]", cause);
+ }
+
+ representation.setConfig(config);
+ }
+
+ @Override
+ public void init(Config.Scope config) {
+
+ }
+
+ @Override
+ public void postInit(KeycloakSessionFactory factory) {
+ factory.register(event -> {
+ });
+ }
+
+ @Override
+ public void close() {
+
+ }
+
+ private void updatePolicy(Policy policy, String groupsClaim, Set groups, AuthorizationProvider authorization) {
+ if (groupsClaim == null) {
+ throw new RuntimeException("Group claims property not provided");
+ }
+
+ if (groups == null || groups.isEmpty()) {
+ throw new RuntimeException("You must provide at least one group");
+ }
+
+ Map config = new HashMap<>(policy.getConfig());
+
+ config.put("groupsClaim", groupsClaim);
+
+ List topLevelGroups = authorization.getRealm().getTopLevelGroups();
+
+ for (GroupPolicyRepresentation.GroupDefinition definition : groups) {
+ GroupModel group = null;
+
+ if (definition.getId() != null) {
+ group = authorization.getRealm().getGroupById(definition.getId());
+ }
+
+ if (group == null) {
+ String path = definition.getPath();
+ String canonicalPath = path.startsWith("/") ? path.substring(1, path.length()) : path;
+
+ if (canonicalPath != null) {
+ String[] parts = canonicalPath.split("/");
+ GroupModel parent = null;
+
+ for (String part : parts) {
+ if (parent == null) {
+ parent = topLevelGroups.stream().filter(groupModel -> groupModel.getName().equals(part)).findFirst().orElseThrow(() -> new RuntimeException("Top level group with name [" + part + "] not found"));
+ } else {
+ group = parent.getSubGroups().stream().filter(groupModel -> groupModel.getName().equals(part)).findFirst().orElseThrow(() -> new RuntimeException("Group with name [" + part + "] not found"));
+ parent = group;
+ }
+ }
+
+ if (parts.length == 1) {
+ group = parent;
+ }
+ }
+ }
+
+ if (group == null) {
+ throw new RuntimeException("Group with id [" + definition.getId() + "] not found");
+ }
+
+ definition.setId(group.getId());
+ definition.setPath(null);
+ }
+
+ try {
+ config.put("groups", JsonSerialization.writeValueAsString(groups));
+ } catch (IOException cause) {
+ throw new RuntimeException("Failed to serialize groups", cause);
+ }
+
+ policy.setConfig(config);
+ }
+
+ private HashSet getGroupsDefinition(Map config) throws IOException {
+ return new HashSet<>(Arrays.asList(JsonSerialization.readValue(config.get("groups"), GroupPolicyRepresentation.GroupDefinition[].class)));
+ }
+}
diff --git a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/js/JSPolicyProvider.java b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/js/JSPolicyProvider.java
index f87573136b..944ae02a90 100644
--- a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/js/JSPolicyProvider.java
+++ b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/js/JSPolicyProvider.java
@@ -17,43 +17,44 @@
*/
package org.keycloak.authorization.policy.provider.js;
-import java.util.function.Supplier;
-
-import javax.script.ScriptEngine;
-import javax.script.ScriptException;
+import java.util.function.BiFunction;
+import org.keycloak.authorization.AuthorizationProvider;
import org.keycloak.authorization.model.Policy;
import org.keycloak.authorization.policy.evaluation.Evaluation;
import org.keycloak.authorization.policy.provider.PolicyProvider;
+import org.keycloak.scripting.EvaluatableScriptAdapter;
/**
* @author Pedro Igor
*/
-public class JSPolicyProvider implements PolicyProvider {
+class JSPolicyProvider implements PolicyProvider {
- private Supplier engineProvider;
+ private final BiFunction evaluatableScript;
- public JSPolicyProvider(Supplier engineProvider) {
- this.engineProvider = engineProvider;
+ JSPolicyProvider(final BiFunction evaluatableScript) {
+ this.evaluatableScript = evaluatableScript;
}
@Override
public void evaluate(Evaluation evaluation) {
- ScriptEngine engine = engineProvider.get();
-
- engine.put("$evaluation", evaluation);
-
Policy policy = evaluation.getPolicy();
+ AuthorizationProvider authorization = evaluation.getAuthorizationProvider();
+ final EvaluatableScriptAdapter adapter = evaluatableScript.apply(authorization, policy);
try {
- engine.eval(policy.getConfig().get("code"));
- } catch (ScriptException e) {
+ //how to deal with long running scripts -> timeout?
+ adapter.eval(bindings -> {
+ bindings.put("script", adapter.getScriptModel());
+ bindings.put("$evaluation", evaluation);
+ });
+ }
+ catch (Exception e) {
throw new RuntimeException("Error evaluating JS Policy [" + policy.getName() + "].", e);
}
}
@Override
public void close() {
-
}
}
diff --git a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/js/JSPolicyProviderFactory.java b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/js/JSPolicyProviderFactory.java
index 3e68d7f603..e3e82ce675 100644
--- a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/js/JSPolicyProviderFactory.java
+++ b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/js/JSPolicyProviderFactory.java
@@ -1,9 +1,5 @@
package org.keycloak.authorization.policy.provider.js;
-import java.util.Map;
-
-import javax.script.ScriptEngineManager;
-
import org.keycloak.Config;
import org.keycloak.authorization.AuthorizationProvider;
import org.keycloak.authorization.model.Policy;
@@ -11,17 +7,20 @@ import org.keycloak.authorization.policy.provider.PolicyProvider;
import org.keycloak.authorization.policy.provider.PolicyProviderFactory;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.ScriptModel;
import org.keycloak.representations.idm.authorization.JSPolicyRepresentation;
import org.keycloak.representations.idm.authorization.PolicyRepresentation;
+import org.keycloak.scripting.EvaluatableScriptAdapter;
+import org.keycloak.scripting.ScriptingProvider;
/**
* @author Pedro Igor
*/
public class JSPolicyProviderFactory implements PolicyProviderFactory {
- private static final String ENGINE = "nashorn";
-
- private JSPolicyProvider provider = new JSPolicyProvider(() -> new ScriptEngineManager().getEngineByName(ENGINE));
+ private final JSPolicyProvider provider = new JSPolicyProvider(this::getEvaluatableScript);
+ private ScriptCache scriptCache;
@Override
public String getName() {
@@ -69,13 +68,16 @@ public class JSPolicyProviderFactory implements PolicyProviderFactory {
+ final ScriptingProvider scripting = authz.getKeycloakSession().getProvider(ScriptingProvider.class);
+ ScriptModel script = getScriptModel(policy, authz.getRealm(), scripting);
+ return scripting.prepareEvaluatableScript(script);
+ });
+ }
+
+ private ScriptModel getScriptModel(final Policy policy, final RealmModel realm, final ScriptingProvider scripting) {
+ String scriptName = policy.getName();
+ String scriptCode = policy.getConfig().get("code");
+ String scriptDescription = policy.getDescription();
+
+ //TODO lookup script by scriptId instead of creating it every time
+ return scripting.createScript(realm.getId(), ScriptModel.TEXT_JAVASCRIPT, scriptName, scriptCode, scriptDescription);
+ }
+
+ private void updatePolicy(Policy policy, String code) {
+ scriptCache.remove(policy.getId());
+ policy.putConfig("code", code);
+ }
}
diff --git a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/js/ScriptCache.java b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/js/ScriptCache.java
new file mode 100644
index 0000000000..4180db6915
--- /dev/null
+++ b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/js/ScriptCache.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright 2017 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.authorization.policy.provider.js;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.locks.LockSupport;
+import java.util.function.Function;
+
+import org.keycloak.scripting.EvaluatableScriptAdapter;
+
+/**
+ * @author Pedro Igor
+ */
+public class ScriptCache {
+
+ /**
+ * The load factor.
+ */
+ private static final float DEFAULT_LOAD_FACTOR = 0.75f;
+
+ private final Map cache;
+
+ private final AtomicBoolean writing = new AtomicBoolean(false);
+
+ private final long maxAge;
+
+ /**
+ * Creates a new instance.
+ *
+ * @param maxEntries the maximum number of entries to keep in the cache
+ */
+ public ScriptCache(int maxEntries) {
+ this(maxEntries, -1);
+ }
+
+ /**
+ * Creates a new instance.
+ *
+ * @param maxEntries the maximum number of entries to keep in the cache
+ * @param maxAge the time in milliseconds that an entry can stay in the cache. If {@code -1}, entries never expire
+ */
+ public ScriptCache(final int maxEntries, long maxAge) {
+ cache = new LinkedHashMap(16, DEFAULT_LOAD_FACTOR, true) {
+ @Override
+ protected boolean removeEldestEntry(Map.Entry eldest) {
+ return cache.size() > maxEntries;
+ }
+ };
+ this.maxAge = maxAge;
+ }
+
+ public EvaluatableScriptAdapter computeIfAbsent(String id, Function function) {
+ try {
+ if (parkForWriteAndCheckInterrupt()) {
+ return null;
+ }
+
+ CacheEntry entry = cache.computeIfAbsent(id, key -> new CacheEntry(key, function.apply(id), maxAge));
+
+ if (entry != null) {
+ return entry.value();
+ }
+
+ return null;
+ } finally {
+ writing.lazySet(false);
+ }
+ }
+
+ public EvaluatableScriptAdapter get(String uri) {
+ if (parkForReadAndCheckInterrupt()) {
+ return null;
+ }
+
+ CacheEntry cached = cache.get(uri);
+
+ if (cached != null) {
+ return removeIfExpired(cached);
+ }
+
+ return null;
+ }
+
+ public void remove(String key) {
+ try {
+ if (parkForWriteAndCheckInterrupt()) {
+ return;
+ }
+
+ cache.remove(key);
+ } finally {
+ writing.lazySet(false);
+ }
+ }
+
+ private EvaluatableScriptAdapter removeIfExpired(CacheEntry cached) {
+ if (cached == null) {
+ return null;
+ }
+
+ if (cached.isExpired()) {
+ remove(cached.key());
+ return null;
+ }
+
+ return cached.value();
+ }
+
+ private boolean parkForWriteAndCheckInterrupt() {
+ while (!writing.compareAndSet(false, true)) {
+ LockSupport.parkNanos(1L);
+ if (Thread.interrupted()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean parkForReadAndCheckInterrupt() {
+ while (writing.get()) {
+ LockSupport.parkNanos(1L);
+ if (Thread.interrupted()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static final class CacheEntry {
+
+ final String key;
+ final EvaluatableScriptAdapter value;
+ final long expiration;
+
+ CacheEntry(String key, EvaluatableScriptAdapter value, long maxAge) {
+ this.key = key;
+ this.value = value;
+ if(maxAge == -1) {
+ expiration = -1;
+ } else {
+ expiration = System.currentTimeMillis() + maxAge;
+ }
+ }
+
+ String key() {
+ return key;
+ }
+
+ EvaluatableScriptAdapter value() {
+ return value;
+ }
+
+ boolean isExpired() {
+ return expiration != -1 ? System.currentTimeMillis() > expiration : false;
+ }
+ }
+}
diff --git a/authz/policy/common/src/main/resources/META-INF/services/org.keycloak.authorization.policy.provider.PolicyProviderFactory b/authz/policy/common/src/main/resources/META-INF/services/org.keycloak.authorization.policy.provider.PolicyProviderFactory
index e4588f87a6..e6fa1cc9e4 100644
--- a/authz/policy/common/src/main/resources/META-INF/services/org.keycloak.authorization.policy.provider.PolicyProviderFactory
+++ b/authz/policy/common/src/main/resources/META-INF/services/org.keycloak.authorization.policy.provider.PolicyProviderFactory
@@ -41,4 +41,5 @@ org.keycloak.authorization.policy.provider.role.RolePolicyProviderFactory
org.keycloak.authorization.policy.provider.scope.ScopePolicyProviderFactory
org.keycloak.authorization.policy.provider.time.TimePolicyProviderFactory
org.keycloak.authorization.policy.provider.user.UserPolicyProviderFactory
-org.keycloak.authorization.policy.provider.client.ClientPolicyProviderFactory
\ No newline at end of file
+org.keycloak.authorization.policy.provider.client.ClientPolicyProviderFactory
+org.keycloak.authorization.policy.provider.group.GroupPolicyProviderFactory
\ No newline at end of file
diff --git a/common/src/main/java/org/keycloak/common/util/ConcurrentMultivaluedHashMap.java b/common/src/main/java/org/keycloak/common/util/ConcurrentMultivaluedHashMap.java
index c092c6f9d1..258f076546 100755
--- a/common/src/main/java/org/keycloak/common/util/ConcurrentMultivaluedHashMap.java
+++ b/common/src/main/java/org/keycloak/common/util/ConcurrentMultivaluedHashMap.java
@@ -31,9 +31,9 @@ public class ConcurrentMultivaluedHashMap extends ConcurrentHashMap list = new CopyOnWriteArrayList<>();
+ List list = createListInstance();
list.add(value);
- put(key, list);
+ put(key, list); // Just override with new List instance
}
public void addAll(K key, V... newValues)
@@ -84,8 +84,15 @@ public class ConcurrentMultivaluedHashMap extends ConcurrentHashMap getList(K key)
{
List list = get(key);
- if (list == null)
- put(key, list = new CopyOnWriteArrayList());
+
+ if (list == null) {
+ list = createListInstance();
+ List existing = putIfAbsent(key, list);
+ if (existing != null) {
+ list = existing;
+ }
+ }
+
return list;
}
@@ -97,4 +104,8 @@ public class ConcurrentMultivaluedHashMap extends ConcurrentHashMap createListInstance() {
+ return new CopyOnWriteArrayList<>();
+ }
+
}
diff --git a/common/src/main/java/org/keycloak/common/util/SystemEnvProperties.java b/common/src/main/java/org/keycloak/common/util/SystemEnvProperties.java
index 1fdfff8d6a..78fc4c7f98 100644
--- a/common/src/main/java/org/keycloak/common/util/SystemEnvProperties.java
+++ b/common/src/main/java/org/keycloak/common/util/SystemEnvProperties.java
@@ -17,6 +17,8 @@
package org.keycloak.common.util;
+import java.util.Collections;
+import java.util.Map;
import java.util.Properties;
/**
@@ -24,9 +26,21 @@ import java.util.Properties;
*/
public class SystemEnvProperties extends Properties {
+ private final Map overrides;
+
+ public SystemEnvProperties(Map overrides) {
+ this.overrides = overrides;
+ }
+
+ public SystemEnvProperties() {
+ this.overrides = Collections.EMPTY_MAP;
+ }
+
@Override
public String getProperty(String key) {
- if (key.startsWith("env.")) {
+ if (overrides.containsKey(key)) {
+ return overrides.get(key);
+ } else if (key.startsWith("env.")) {
return System.getenv().get(key.substring(4));
} else {
return System.getProperty(key);
diff --git a/core/src/main/java/org/keycloak/representations/idm/authorization/GroupPolicyRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/authorization/GroupPolicyRepresentation.java
new file mode 100644
index 0000000000..c063f8f87b
--- /dev/null
+++ b/core/src/main/java/org/keycloak/representations/idm/authorization/GroupPolicyRepresentation.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright 2017 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.idm.authorization;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+/**
+ * @author Pedro Igor
+ */
+public class GroupPolicyRepresentation extends AbstractPolicyRepresentation {
+
+ private String groupsClaim;
+ private Set groups;
+
+ @Override
+ public String getType() {
+ return "group";
+ }
+
+ public String getGroupsClaim() {
+ return groupsClaim;
+ }
+
+ public void setGroupsClaim(String groupsClaim) {
+ this.groupsClaim = groupsClaim;
+ }
+
+ public Set getGroups() {
+ return groups;
+ }
+
+ public void setGroups(Set groups) {
+ this.groups = groups;
+ }
+
+ public void addGroup(String... ids) {
+ for (String id : ids) {
+ addGroup(id, false);
+ }
+ }
+
+ public void addGroup(String id, boolean extendChildren) {
+ if (groups == null) {
+ groups = new HashSet<>();
+ }
+ groups.add(new GroupDefinition(id, extendChildren));
+ }
+
+ public void addGroupPath(String... paths) {
+ for (String path : paths) {
+ addGroupPath(path, false);
+ }
+ }
+
+ public void addGroupPath(String path, boolean extendChildren) {
+ if (groups == null) {
+ groups = new HashSet<>();
+ }
+ groups.add(new GroupDefinition(null, path, extendChildren));
+ }
+
+ public void removeGroup(String... ids) {
+ if (groups != null) {
+ for (final String id : ids) {
+ if (!groups.remove(id)) {
+ for (GroupDefinition group : new HashSet<>(groups)) {
+ if (group.getPath().startsWith(id)) {
+ groups.remove(group);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ public static class GroupDefinition {
+
+ private String id;
+ private String path;
+ private boolean extendChildren;
+
+ public GroupDefinition() {
+ this(null);
+ }
+
+ public GroupDefinition(String id) {
+ this(id, false);
+ }
+
+ public GroupDefinition(String id, boolean extendChildren) {
+ this(id, null, extendChildren);
+ }
+
+ public GroupDefinition(String id, String path, boolean extendChildren) {
+ this.id = id;
+ this.path = path;
+ this.extendChildren = extendChildren;
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public String getPath() {
+ return path;
+ }
+
+ public void setPath(String path) {
+ this.path = path;
+ }
+
+ public boolean isExtendChildren() {
+ return extendChildren;
+ }
+
+ public void setExtendChildren(boolean extendChildren) {
+ this.extendChildren = extendChildren;
+ }
+ }
+}
diff --git a/examples/demo-template/README.md b/examples/demo-template/README.md
index 52b3001be1..950b2266c9 100755
--- a/examples/demo-template/README.md
+++ b/examples/demo-template/README.md
@@ -202,28 +202,7 @@ An Angular JS example using Keycloak to secure it.
If you are already logged in, you will not be asked for a username and password, but you will be redirected to
an oauth grant page. This page asks you if you want to grant certain permissions to the third-part app.
-Step 10: Angular2 JS Example
-----------------------------------
-An Angular2 JS example using Keycloak to secure it. Angular2 is in beta version yet.
-
-To install angular2
-```
-$ cd keycloak/examples/demo-template/angular2-product-app/src/main/webapp/
-$ npm install
-```
-
-Transpile TypeScript to JavaScript before running the application.
-```
-$ npm run tsc
-```
-
-[http://localhost:8080/angular2-product](http://localhost:8080/angular2-product)
-
-If you are already logged in, you will not be asked for a username and password, but you will be redirected to
-an oauth grant page. This page asks you if you want to grant certain permissions to the third-part app.
-
-
-Step 11: Pure HTML5/Javascript Example
+Step 10: Pure HTML5/Javascript Example
----------------------------------
An pure HTML5/Javascript example using Keycloak to secure it.
@@ -232,7 +211,7 @@ An pure HTML5/Javascript example using Keycloak to secure it.
If you are already logged in, you will not be asked for a username and password, but you will be redirected to
an oauth grant page. This page asks you if you want to grant certain permissions to the third-part app.
-Step 12: Service Account Example
+Step 11: Service Account Example
================================
An example for retrieve service account dedicated to the Client Application itself (not to any user).
@@ -240,7 +219,7 @@ An example for retrieve service account dedicated to the Client Application itse
Client authentication is done with OAuth2 Client Credentials Grant in out-of-bound request (Not Keycloak login screen displayed) .
-Step 13: Offline Access Example
+Step 12: Offline Access Example
===============================
An example for retrieve offline token, which is then saved to the database and can be used by application anytime later. Offline token
is valid even if user is already logged out from SSO. Server restart also won't invalidate offline token. Offline token can be revoked by the user in
diff --git a/examples/demo-template/angular2-product-app/pom.xml b/examples/demo-template/angular2-product-app/pom.xml
deleted file mode 100644
index c62fe16b68..0000000000
--- a/examples/demo-template/angular2-product-app/pom.xml
+++ /dev/null
@@ -1,101 +0,0 @@
-
-
-
-
-
- keycloak-examples-demo-parent
- org.keycloak
- 3.2.0.CR1-SNAPSHOT
-
-
- 4.0.0
- org.keycloak.example.demo
- angular2-product-example
- war
- Angular2 Product Portal JS
-
-
-
- angular2-product
-
-
- org.jboss.as.plugins
- jboss-as-maven-plugin
-
- false
-
-
-
- org.wildfly.plugins
- wildfly-maven-plugin
-
- false
-
-
-
- org.apache.maven.plugins
- maven-war-plugin
-
-
-
-
- src/main/frontend/dist
-
-
-
-
-
- com.github.eirslett
- frontend-maven-plugin
- 1.3
-
- v6.10.0
- src/main/frontend
- target
-
-
-
- install node and npm
-
- install-node-and-npm
-
-
-
- npm install
-
- npm
-
-
- install
-
-
-
- npm run build
-
- npm
-
-
- run ng -- build --base-href /angular2-product/ --env=war
-
-
-
-
-
-
-
diff --git a/examples/demo-template/angular2-product-app/src/main/frontend/.angular-cli.json b/examples/demo-template/angular2-product-app/src/main/frontend/.angular-cli.json
deleted file mode 100644
index 1d5d927a6c..0000000000
--- a/examples/demo-template/angular2-product-app/src/main/frontend/.angular-cli.json
+++ /dev/null
@@ -1,57 +0,0 @@
-{
- "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
- "project": {
- "version": "1.0.0-beta.32.3",
- "name": "angular2-product-app"
- },
- "apps": [
- {
- "root": "src",
- "outDir": "dist",
- "assets": [
- "assets",
- "favicon.ico"
- ],
- "index": "index.html",
- "main": "main.ts",
- "polyfills": "polyfills.ts",
- "test": "test.ts",
- "tsconfig": "tsconfig.json",
- "prefix": "app",
- "styles": [
- "styles.css"
- ],
- "scripts": [],
- "environmentSource": "environments/environment.ts",
- "environments": {
- "dev": "environments/environment.ts",
- "war": "environments/environment.war.ts",
- "prod": "environments/environment.prod.ts"
- }
- }
- ],
- "e2e": {
- "protractor": {
- "config": "./protractor.conf.js"
- }
- },
- "lint": [
- {
- "files": "src/**/*.ts",
- "project": "src/tsconfig.json"
- },
- {
- "files": "e2e/**/*.ts",
- "project": "e2e/tsconfig.json"
- }
- ],
- "test": {
- "karma": {
- "config": "./karma.conf.js"
- }
- },
- "defaults": {
- "styleExt": "css",
- "component": {}
- }
-}
diff --git a/examples/demo-template/angular2-product-app/src/main/frontend/.editorconfig b/examples/demo-template/angular2-product-app/src/main/frontend/.editorconfig
deleted file mode 100644
index 6e87a003da..0000000000
--- a/examples/demo-template/angular2-product-app/src/main/frontend/.editorconfig
+++ /dev/null
@@ -1,13 +0,0 @@
-# Editor configuration, see http://editorconfig.org
-root = true
-
-[*]
-charset = utf-8
-indent_style = space
-indent_size = 2
-insert_final_newline = true
-trim_trailing_whitespace = true
-
-[*.md]
-max_line_length = off
-trim_trailing_whitespace = false
diff --git a/examples/demo-template/angular2-product-app/src/main/frontend/.gitignore b/examples/demo-template/angular2-product-app/src/main/frontend/.gitignore
deleted file mode 100644
index 8ce8738573..0000000000
--- a/examples/demo-template/angular2-product-app/src/main/frontend/.gitignore
+++ /dev/null
@@ -1,41 +0,0 @@
-# See http://help.github.com/ignore-files/ for more about ignoring files.
-
-# compiled output
-/dist
-/tmp
-
-# dependencies
-/node_modules
-
-# IDEs and editors
-/.idea
-.project
-.classpath
-.c9/
-*.launch
-.settings/
-*.sublime-workspace
-
-# IDE - VSCode
-.vscode/*
-!.vscode/settings.json
-!.vscode/tasks.json
-!.vscode/launch.json
-!.vscode/extensions.json
-
-# misc
-/.sass-cache
-/connect.lock
-/coverage/*
-/libpeerconnection.log
-npm-debug.log
-testem.log
-/typings
-
-# e2e
-/e2e/*.js
-/e2e/*.map
-
-#System Files
-.DS_Store
-Thumbs.db
diff --git a/examples/demo-template/angular2-product-app/src/main/frontend/README.md b/examples/demo-template/angular2-product-app/src/main/frontend/README.md
deleted file mode 100644
index f0520d74ee..0000000000
--- a/examples/demo-template/angular2-product-app/src/main/frontend/README.md
+++ /dev/null
@@ -1,36 +0,0 @@
-# Angular2ProductApp
-
-This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 1.0.0-beta.32.3. Keycloak integration is based on Angular
-
-## Development server
-Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.
-
-This application depends on `database-service` application API deployed at `http://localhost:8080/database-service` so you will have to make sure it allows cross-origin requests. It can be enabled for `database-service` in it's `keycloak.json`:
-
- {
- "realm" : "demo",
- "resource" : "database-service",
- ...
- "enable-cors": true
- }
-
-## Code scaffolding
-
-Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive/pipe/service/class/module`.
-
-## Build
-
-Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `-prod` flag for a production build.
-
-## Running unit tests
-
-Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
-
-## Running end-to-end tests
-
-Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/).
-Before running the tests make sure you are serving the app via `ng serve` and Keycloak and `database-service` is up and running at `http://localhost:8080`.
-
-## Further help
-
-To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md).
diff --git a/examples/demo-template/angular2-product-app/src/main/frontend/e2e/app.e2e-spec.ts b/examples/demo-template/angular2-product-app/src/main/frontend/e2e/app.e2e-spec.ts
deleted file mode 100644
index 83246f10dd..0000000000
--- a/examples/demo-template/angular2-product-app/src/main/frontend/e2e/app.e2e-spec.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-import { Angular2ProductAppPage } from './app.po';
-
-describe('angular2-product-app App', () => {
- let page: Angular2ProductAppPage;
-
- beforeEach(() => {
- page = new Angular2ProductAppPage();
- });
-
- it('should display message saying Angular2 Product', () => {
- page.navigateTo();
- expect(page.getParagraphText()).toEqual('Angular2 Product');
- });
-
- it('should load Products', () => {
- page.navigateTo();
- const products = page.loadProducts();
- ['iphone', 'ipad', 'ipod'].forEach(e => expect(products).toContain(e));
- });
-});
diff --git a/examples/demo-template/angular2-product-app/src/main/frontend/e2e/app.po.ts b/examples/demo-template/angular2-product-app/src/main/frontend/e2e/app.po.ts
deleted file mode 100644
index 8ebb121c33..0000000000
--- a/examples/demo-template/angular2-product-app/src/main/frontend/e2e/app.po.ts
+++ /dev/null
@@ -1,28 +0,0 @@
-import { browser, element, by } from 'protractor';
-
-export class Angular2ProductAppPage {
- navigateTo() {
- browser.ignoreSynchronization = true;
- browser.get('/');
- browser.getCurrentUrl().then(url => {
- if (url.includes('/auth/realms/demo')) {
- element(by.id('username')).sendKeys('bburke@redhat.com');
- element(by.id('password')).sendKeys('password');
- element(by.id('kc-login')).click();
- }
- browser.ignoreSynchronization = false;
-
- });
- }
-
- getParagraphText() {
- return element(by.css('app-root h1')).getText();
- }
-
- loadProducts() {
- const click = element(by.id('reload-data')).click();
- browser.wait(click, 2000, 'Products should load within 2 seconds');
- return element.all(by.css('table.table td')).getText();
- }
-
-}
diff --git a/examples/demo-template/angular2-product-app/src/main/frontend/e2e/tsconfig.json b/examples/demo-template/angular2-product-app/src/main/frontend/e2e/tsconfig.json
deleted file mode 100644
index 94da47a242..0000000000
--- a/examples/demo-template/angular2-product-app/src/main/frontend/e2e/tsconfig.json
+++ /dev/null
@@ -1,19 +0,0 @@
-{
- "compileOnSave": false,
- "compilerOptions": {
- "declaration": false,
- "emitDecoratorMetadata": true,
- "experimentalDecorators": true,
- "lib": [
- "es2016"
- ],
- "module": "commonjs",
- "moduleResolution": "node",
- "outDir": "../dist/out-tsc-e2e",
- "sourceMap": true,
- "target": "es6",
- "typeRoots": [
- "../node_modules/@types"
- ]
- }
-}
diff --git a/examples/demo-template/angular2-product-app/src/main/frontend/karma.conf.js b/examples/demo-template/angular2-product-app/src/main/frontend/karma.conf.js
deleted file mode 100644
index b0052c5231..0000000000
--- a/examples/demo-template/angular2-product-app/src/main/frontend/karma.conf.js
+++ /dev/null
@@ -1,45 +0,0 @@
-// Karma configuration file, see link for more information
-// https://karma-runner.github.io/0.13/config/configuration-file.html
-
-module.exports = function (config) {
- config.set({
- basePath: '',
- frameworks: ['jasmine', '@angular/cli'],
- plugins: [
- require('karma-jasmine'),
- require('karma-chrome-launcher'),
- require('karma-jasmine-html-reporter'),
- require('karma-coverage-istanbul-reporter'),
- require('@angular/cli/plugins/karma')
- ],
- client:{
- clearContext: false // leave Jasmine Spec Runner output visible in browser
- },
- files: [
- { pattern: './src/test.ts', watched: false }
- ],
- preprocessors: {
- './src/test.ts': ['@angular/cli']
- },
- mime: {
- 'text/x-typescript': ['ts','tsx']
- },
- coverageIstanbulReporter: {
- reports: [ 'html', 'lcovonly' ],
- fixWebpackSourcePaths: true
- },
- angularCli: {
- config: './.angular-cli.json',
- environment: 'dev'
- },
- reporters: config.angularCli && config.angularCli.codeCoverage
- ? ['progress', 'coverage-istanbul']
- : ['progress', 'kjhtml'],
- port: 9876,
- colors: true,
- logLevel: config.LOG_INFO,
- autoWatch: true,
- browsers: ['Chrome'],
- singleRun: false
- });
-};
diff --git a/examples/demo-template/angular2-product-app/src/main/frontend/package.json b/examples/demo-template/angular2-product-app/src/main/frontend/package.json
deleted file mode 100644
index 91223e5e80..0000000000
--- a/examples/demo-template/angular2-product-app/src/main/frontend/package.json
+++ /dev/null
@@ -1,46 +0,0 @@
-{
- "name": "angular2-product-app",
- "version": "0.0.0",
- "license": "Apache-2.0",
- "angular-cli": {},
- "scripts": {
- "ng": "ng",
- "start": "ng serve",
- "test": "ng test",
- "lint": "ng lint",
- "e2e": "ng e2e"
- },
- "private": true,
- "dependencies": {
- "@angular/common": "^2.4.0",
- "@angular/compiler": "^2.4.0",
- "@angular/core": "^2.4.0",
- "@angular/forms": "^2.4.0",
- "@angular/http": "^2.4.0",
- "@angular/platform-browser": "^2.4.0",
- "@angular/platform-browser-dynamic": "^2.4.0",
- "@angular/router": "^3.4.0",
- "core-js": "^2.4.1",
- "rxjs": "^5.1.0",
- "zone.js": "^0.7.6"
- },
- "devDependencies": {
- "@angular/cli": "1.0.0-beta.32.3",
- "@angular/compiler-cli": "^2.4.0",
- "@types/jasmine": "2.5.38",
- "@types/node": "~6.0.60",
- "codelyzer": "~2.0.0-beta.4",
- "jasmine-core": "~2.5.2",
- "jasmine-spec-reporter": "~3.2.0",
- "karma": "~1.4.1",
- "karma-chrome-launcher": "~2.0.0",
- "karma-cli": "~1.0.1",
- "karma-jasmine": "~1.1.0",
- "karma-jasmine-html-reporter": "^0.2.2",
- "karma-coverage-istanbul-reporter": "^0.2.0",
- "protractor": "~5.1.0",
- "ts-node": "~2.0.0",
- "tslint": "~4.4.2",
- "typescript": "~2.0.0"
- }
-}
diff --git a/examples/demo-template/angular2-product-app/src/main/frontend/protractor.conf.js b/examples/demo-template/angular2-product-app/src/main/frontend/protractor.conf.js
deleted file mode 100644
index c819669c0a..0000000000
--- a/examples/demo-template/angular2-product-app/src/main/frontend/protractor.conf.js
+++ /dev/null
@@ -1,31 +0,0 @@
-// Protractor configuration file, see link for more information
-// https://github.com/angular/protractor/blob/master/lib/config.ts
-
-/*global jasmine */
-const { SpecReporter } = require('jasmine-spec-reporter');
-
-exports.config = {
- allScriptsTimeout: 11000,
- specs: [
- './e2e/**/*.e2e-spec.ts'
- ],
- capabilities: {
- 'browserName': 'chrome'
- },
- directConnect: true,
- baseUrl: 'http://localhost:4200/',
- framework: 'jasmine',
- jasmineNodeOpts: {
- showColors: true,
- defaultTimeoutInterval: 30000,
- print: function() {}
- },
- beforeLaunch: function() {
- require('ts-node').register({
- project: 'e2e'
- });
- },
- onPrepare() {
- jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
- }
-};
diff --git a/examples/demo-template/angular2-product-app/src/main/frontend/src/app/app.component.css b/examples/demo-template/angular2-product-app/src/main/frontend/src/app/app.component.css
deleted file mode 100644
index e69de29bb2..0000000000
diff --git a/examples/demo-template/angular2-product-app/src/main/frontend/src/app/app.component.html b/examples/demo-template/angular2-product-app/src/main/frontend/src/app/app.component.html
deleted file mode 100644
index 163d8db9b4..0000000000
--- a/examples/demo-template/angular2-product-app/src/main/frontend/src/app/app.component.html
+++ /dev/null
@@ -1,20 +0,0 @@
-
-
-
{{title}}
-
Products
-
-
-
-
-
-
Product Listing
-
-
-
-
-
{{p}}
-
-
-
-
-
diff --git a/examples/demo-template/angular2-product-app/src/main/frontend/src/app/app.component.spec.ts b/examples/demo-template/angular2-product-app/src/main/frontend/src/app/app.component.spec.ts
deleted file mode 100644
index f5fc6c1dea..0000000000
--- a/examples/demo-template/angular2-product-app/src/main/frontend/src/app/app.component.spec.ts
+++ /dev/null
@@ -1,66 +0,0 @@
-import { TestBed, async } from '@angular/core/testing';
-import { AppComponent } from './app.component';
-import { KeycloakService } from './keycloak/keycloak.service';
-import {
- HttpModule,
- XHRBackend,
- ResponseOptions,
- Response,
- RequestMethod
-} from '@angular/http';
-import {
- MockBackend,
- MockConnection
-} from '@angular/http/testing/mock_backend';
-
-
-describe('AppComponent', () => {
- beforeEach(() => {
- TestBed.configureTestingModule({
- imports: [HttpModule],
- providers: [
- {
- provide: XHRBackend,
- useClass: MockBackend
- },
- {
- provide: KeycloakService
- }
- ],
- declarations: [
- AppComponent
- ],
- });
- TestBed.compileComponents();
- });
-
- it('should create the app', async(() => {
- const fixture = TestBed.createComponent(AppComponent);
- const app = fixture.debugElement.componentInstance;
- expect(app).toBeTruthy();
- }));
-
- it(`should have as title 'Angular2 Product'`, async(() => {
- const fixture = TestBed.createComponent(AppComponent);
- const app = fixture.debugElement.componentInstance;
- expect(app.title).toEqual('Angular2 Product');
- }));
-
- it('should render title in a h1 tag', async(() => {
- const fixture = TestBed.createComponent(AppComponent);
- fixture.detectChanges();
- const compiled = fixture.debugElement.nativeElement;
- expect(compiled.querySelector('h1').textContent).toContain('Angular2 Product');
- }));
-
- it('should render product list', async(() => {
- const fixture = TestBed.createComponent(AppComponent);
- fixture.componentInstance.products = ['iphone', 'ipad', 'ipod'];
- fixture.detectChanges();
- const compiled = fixture.debugElement.nativeElement;
- expect(compiled.querySelector('table thead tr th').textContent).toContain('Product Listing');
- expect(compiled.querySelectorAll('table tbody tr td')[0].textContent).toContain('iphone');
- expect(compiled.querySelectorAll('table tbody tr td')[1].textContent).toContain('ipad');
- expect(compiled.querySelectorAll('table tbody tr td')[2].textContent).toContain('ipod');
- }));
-});
diff --git a/examples/demo-template/angular2-product-app/src/main/frontend/src/app/app.component.ts b/examples/demo-template/angular2-product-app/src/main/frontend/src/app/app.component.ts
deleted file mode 100644
index 28b9b98202..0000000000
--- a/examples/demo-template/angular2-product-app/src/main/frontend/src/app/app.component.ts
+++ /dev/null
@@ -1,32 +0,0 @@
-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/keycloak.service';
-
-import { environment } from '../environments/environment';
-
-@Component({
- selector: 'app-root',
- templateUrl: './app.component.html',
- styleUrls: ['./app.component.css']
-})
-export class AppComponent {
- title = 'Angular2 Product';
-
- products: string[] = [];
-
- constructor(private http: Http, private kc: KeycloakService) {}
-
- logout() {
- this.kc.logout();
- }
-
- reloadData() {
- this.http.get(environment.serviceBaseUrl + '/products')
- .map(res => res.json())
- .subscribe(prods => this.products = prods,
- error => console.log(error));
- }
-}
diff --git a/examples/demo-template/angular2-product-app/src/main/frontend/src/app/app.module.ts b/examples/demo-template/angular2-product-app/src/main/frontend/src/app/app.module.ts
deleted file mode 100644
index 99a60f9171..0000000000
--- a/examples/demo-template/angular2-product-app/src/main/frontend/src/app/app.module.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-import { BrowserModule } from '@angular/platform-browser';
-import { NgModule } from '@angular/core';
-import { FormsModule } from '@angular/forms';
-import { HttpModule } from '@angular/http';
-import { KeycloakService } from './keycloak/keycloak.service';
-import { KeycloakHttp, KEYCLOAK_HTTP_PROVIDER } from './keycloak/keycloak.http';
-import { AppComponent } from './app.component';
-
-@NgModule({
- declarations: [
- AppComponent
- ],
- imports: [
- BrowserModule,
- FormsModule,
- HttpModule
- ],
- providers: [
- KeycloakService,
- KEYCLOAK_HTTP_PROVIDER
- ],
- bootstrap: [AppComponent]
-})
-export class AppModule { }
diff --git a/examples/demo-template/angular2-product-app/src/main/frontend/src/app/keycloak/keycloak.http.spec.ts b/examples/demo-template/angular2-product-app/src/main/frontend/src/app/keycloak/keycloak.http.spec.ts
deleted file mode 100644
index 7ea175e076..0000000000
--- a/examples/demo-template/angular2-product-app/src/main/frontend/src/app/keycloak/keycloak.http.spec.ts
+++ /dev/null
@@ -1,47 +0,0 @@
-import {Injectable, ReflectiveInjector} from '@angular/core';
-import {async, fakeAsync, tick} from '@angular/core/testing';
-import {BaseRequestOptions, ConnectionBackend, Http, RequestOptions} from '@angular/http';
-import {Response, ResponseOptions} from '@angular/http';
-import {MockBackend, MockConnection} from '@angular/http/testing';
-
-import { KeycloakHttp, KEYCLOAK_HTTP_PROVIDER, keycloakHttpFactory } from './keycloak.http';
-import { KeycloakService } from './keycloak.service';
-
-@Injectable()
-class MockKeycloakService extends KeycloakService {
- getToken(): Promise {
- return Promise.resolve('hello');
- }
-}
-
-describe('KeycloakHttp', () => {
-
- let injector: ReflectiveInjector;
- let backend: MockBackend;
- let lastConnection: MockConnection;
- let http: Http;
-
- beforeEach(() => {
- injector = ReflectiveInjector.resolveAndCreate([
- {provide: ConnectionBackend, useClass: MockBackend},
- {provide: RequestOptions, useClass: BaseRequestOptions},
- {provide: KeycloakService, useClass: MockKeycloakService},
- {
- provide: Http,
- useFactory: keycloakHttpFactory,
- deps: [ConnectionBackend, RequestOptions, KeycloakService]
- }
- ]);
- http = injector.get(Http);
- backend = injector.get(ConnectionBackend) as MockBackend;
- backend.connections.subscribe((c: MockConnection) => lastConnection = c);
- });
-
- it('should set Authorization header', fakeAsync(() => {
- http.get('foo').subscribe(r => console.log(r));
- tick();
- expect(lastConnection).toBeDefined('no http service connection at all?');
- expect(lastConnection.request.headers.get('Authorization')).toBe('Bearer hello');
- }));
-
-});
diff --git a/examples/demo-template/angular2-product-app/src/main/frontend/src/app/keycloak/keycloak.http.ts b/examples/demo-template/angular2-product-app/src/main/frontend/src/app/keycloak/keycloak.http.ts
deleted file mode 100644
index 07887bdd01..0000000000
--- a/examples/demo-template/angular2-product-app/src/main/frontend/src/app/keycloak/keycloak.http.ts
+++ /dev/null
@@ -1,42 +0,0 @@
-import {Injectable} from '@angular/core';
-import {Http, Request, XHRBackend, ConnectionBackend, RequestOptions, RequestOptionsArgs, Response, Headers} from '@angular/http';
-
-import {KeycloakService} from './keycloak.service';
-import {Observable} from 'rxjs/Rx';
-
-/**
- * This provides a wrapper over the ng2 Http class that insures tokens are refreshed on each request.
- */
-@Injectable()
-export class KeycloakHttp extends Http {
- constructor(_backend: ConnectionBackend, _defaultOptions: RequestOptions, private _keycloakService: KeycloakService) {
- super(_backend, _defaultOptions);
- }
-
- request(url: string | Request, options?: RequestOptionsArgs): Observable {
- const tokenPromise: Promise = this._keycloakService.getToken();
- const tokenObservable: Observable = Observable.fromPromise(tokenPromise);
-
- if (typeof url === 'string') {
- return tokenObservable.map(token => {
- const authOptions = new RequestOptions({headers: new Headers({'Authorization': 'Bearer ' + token})});
- return new RequestOptions().merge(options).merge(authOptions);
- }).concatMap(opts => super.request(url, opts));
- } else if (url instanceof Request) {
- return tokenObservable.map(token => {
- url.headers.set('Authorization', 'Bearer ' + token);
- return url;
- }).concatMap(request => super.request(request));
- }
- }
-}
-
-export function keycloakHttpFactory(backend: XHRBackend, defaultOptions: RequestOptions, keycloakService: KeycloakService) {
- return new KeycloakHttp(backend, defaultOptions, keycloakService);
-}
-
-export const KEYCLOAK_HTTP_PROVIDER = {
- provide: Http,
- useFactory: keycloakHttpFactory,
- deps: [XHRBackend, RequestOptions, KeycloakService]
-};
diff --git a/examples/demo-template/angular2-product-app/src/main/frontend/src/app/keycloak/keycloak.service.ts b/examples/demo-template/angular2-product-app/src/main/frontend/src/app/keycloak/keycloak.service.ts
deleted file mode 100644
index 9ecf4469ad..0000000000
--- a/examples/demo-template/angular2-product-app/src/main/frontend/src/app/keycloak/keycloak.service.ts
+++ /dev/null
@@ -1,60 +0,0 @@
-import {Injectable} from '@angular/core';
-
-import { environment } from '../../environments/environment';
-
-declare var Keycloak: any;
-
-@Injectable()
-export class KeycloakService {
- static auth: any = {};
-
- static init(): Promise {
- const keycloakAuth: any = Keycloak({
- url: environment.keykloakBaseUrl,
- realm: 'demo',
- clientId: 'angular2-product',
- });
-
- 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='
- + document.baseURI;
- 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');
- });
- } else {
- reject('Not loggen in');
- }
- });
- }
-}
diff --git a/examples/demo-template/angular2-product-app/src/main/frontend/src/assets/.gitkeep b/examples/demo-template/angular2-product-app/src/main/frontend/src/assets/.gitkeep
deleted file mode 100644
index e69de29bb2..0000000000
diff --git a/examples/demo-template/angular2-product-app/src/main/frontend/src/environments/environment.prod.ts b/examples/demo-template/angular2-product-app/src/main/frontend/src/environments/environment.prod.ts
deleted file mode 100644
index 57a8df3cec..0000000000
--- a/examples/demo-template/angular2-product-app/src/main/frontend/src/environments/environment.prod.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-export const environment = {
- production: true,
- keykloakBaseUrl: 'http://localhost:8080/auth',
- serviceBaseUrl: 'http://localhost:8080/database'
-};
diff --git a/examples/demo-template/angular2-product-app/src/main/frontend/src/environments/environment.ts b/examples/demo-template/angular2-product-app/src/main/frontend/src/environments/environment.ts
deleted file mode 100644
index f93bdd5dd7..0000000000
--- a/examples/demo-template/angular2-product-app/src/main/frontend/src/environments/environment.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-// The file contents for the current environment will overwrite these during build.
-// The build system defaults to the dev environment which uses `environment.ts`, but if you do
-// `ng build --env=prod` then `environment.prod.ts` will be used instead.
-// The list of which env maps to which file can be found in `.angular-cli.json`.
-
-export const environment = {
- production: false,
- keykloakBaseUrl: 'http://localhost:8080/auth',
- serviceBaseUrl: 'http://localhost:8080/database'
-};
diff --git a/examples/demo-template/angular2-product-app/src/main/frontend/src/environments/environment.war.ts b/examples/demo-template/angular2-product-app/src/main/frontend/src/environments/environment.war.ts
deleted file mode 100644
index 6bb464d425..0000000000
--- a/examples/demo-template/angular2-product-app/src/main/frontend/src/environments/environment.war.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-export const environment = {
- production: false,
- keykloakBaseUrl: '/auth',
- serviceBaseUrl: '/database'
-};
diff --git a/examples/demo-template/angular2-product-app/src/main/frontend/src/favicon.ico b/examples/demo-template/angular2-product-app/src/main/frontend/src/favicon.ico
deleted file mode 100644
index 8081c7ceaf..0000000000
Binary files a/examples/demo-template/angular2-product-app/src/main/frontend/src/favicon.ico and /dev/null differ
diff --git a/examples/demo-template/angular2-product-app/src/main/frontend/src/index.html b/examples/demo-template/angular2-product-app/src/main/frontend/src/index.html
deleted file mode 100644
index 5b15f36cd3..0000000000
--- a/examples/demo-template/angular2-product-app/src/main/frontend/src/index.html
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
-
-
- Angular2ProductApp
-
-
-
-
-
-
-
- Loading...
-
-
diff --git a/examples/demo-template/angular2-product-app/src/main/frontend/src/main.ts b/examples/demo-template/angular2-product-app/src/main/frontend/src/main.ts
deleted file mode 100644
index 8bd5ab58e1..0000000000
--- a/examples/demo-template/angular2-product-app/src/main/frontend/src/main.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-import { enableProdMode } from '@angular/core';
-import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
-
-import { AppModule } from './app/app.module';
-import { environment } from './environments/environment';
-import { KeycloakService } from './app/keycloak/keycloak.service';
-
-if (environment.production) {
- enableProdMode();
-}
-
-KeycloakService.init()
- .then(() => platformBrowserDynamic().bootstrapModule(AppModule))
- .catch(e => window.location.reload());
diff --git a/examples/demo-template/angular2-product-app/src/main/frontend/src/polyfills.ts b/examples/demo-template/angular2-product-app/src/main/frontend/src/polyfills.ts
deleted file mode 100644
index 53bdaf1b86..0000000000
--- a/examples/demo-template/angular2-product-app/src/main/frontend/src/polyfills.ts
+++ /dev/null
@@ -1,68 +0,0 @@
-/**
- * This file includes polyfills needed by Angular and is loaded before the app.
- * You can add your own extra polyfills to this file.
- *
- * This file is divided into 2 sections:
- * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
- * 2. Application imports. Files imported after ZoneJS that should be loaded before your main
- * file.
- *
- * The current setup is for so-called "evergreen" browsers; the last versions of browsers that
- * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
- * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
- *
- * Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html
- */
-
-/***************************************************************************************************
- * BROWSER POLYFILLS
- */
-
-/** IE9, IE10 and IE11 requires all of the following polyfills. **/
-// import 'core-js/es6/symbol';
-// import 'core-js/es6/object';
-// import 'core-js/es6/function';
-// import 'core-js/es6/parse-int';
-// import 'core-js/es6/parse-float';
-// import 'core-js/es6/number';
-// import 'core-js/es6/math';
-// import 'core-js/es6/string';
-// import 'core-js/es6/date';
-// import 'core-js/es6/array';
-// import 'core-js/es6/regexp';
-// import 'core-js/es6/map';
-// import 'core-js/es6/set';
-
-/** IE10 and IE11 requires the following for NgClass support on SVG elements */
-// import 'classlist.js'; // Run `npm install --save classlist.js`.
-
-/** IE10 and IE11 requires the following to support `@angular/animation`. */
-// import 'web-animations-js'; // Run `npm install --save web-animations-js`.
-
-
-/** Evergreen browsers require these. **/
-import 'core-js/es6/reflect';
-import 'core-js/es7/reflect';
-
-
-/** ALL Firefox browsers require the following to support `@angular/animation`. **/
-// import 'web-animations-js'; // Run `npm install --save web-animations-js`.
-
-
-
-/***************************************************************************************************
- * Zone JS is required by Angular itself.
- */
-import 'zone.js/dist/zone'; // Included with Angular CLI.
-
-
-
-/***************************************************************************************************
- * APPLICATION IMPORTS
- */
-
-/**
- * Date, currency, decimal and percent pipes.
- * Needed for: All but Chrome, Firefox, Edge, IE11 and Safari 10
- */
-// import 'intl'; // Run `npm install --save intl`.
diff --git a/examples/demo-template/angular2-product-app/src/main/frontend/src/styles.css b/examples/demo-template/angular2-product-app/src/main/frontend/src/styles.css
deleted file mode 100644
index 90d4ee0072..0000000000
--- a/examples/demo-template/angular2-product-app/src/main/frontend/src/styles.css
+++ /dev/null
@@ -1 +0,0 @@
-/* You can add global styles to this file, and also import other style files */
diff --git a/examples/demo-template/angular2-product-app/src/main/frontend/src/test.ts b/examples/demo-template/angular2-product-app/src/main/frontend/src/test.ts
deleted file mode 100644
index 9bf72267e9..0000000000
--- a/examples/demo-template/angular2-product-app/src/main/frontend/src/test.ts
+++ /dev/null
@@ -1,32 +0,0 @@
-// This file is required by karma.conf.js and loads recursively all the .spec and framework files
-
-import 'zone.js/dist/long-stack-trace-zone';
-import 'zone.js/dist/proxy.js';
-import 'zone.js/dist/sync-test';
-import 'zone.js/dist/jasmine-patch';
-import 'zone.js/dist/async-test';
-import 'zone.js/dist/fake-async-test';
-import { getTestBed } from '@angular/core/testing';
-import {
- BrowserDynamicTestingModule,
- platformBrowserDynamicTesting
-} from '@angular/platform-browser-dynamic/testing';
-
-// Unfortunately there's no typing for the `__karma__` variable. Just declare it as any.
-declare var __karma__: any;
-declare var require: any;
-
-// Prevent Karma from running prematurely.
-__karma__.loaded = function () {};
-
-// First, initialize the Angular testing environment.
-getTestBed().initTestEnvironment(
- BrowserDynamicTestingModule,
- platformBrowserDynamicTesting()
-);
-// Then we find all the tests.
-const context = require.context('./', true, /\.spec\.ts$/);
-// And load the modules.
-context.keys().map(context);
-// Finally, start Karma to run the tests.
-__karma__.start();
diff --git a/examples/demo-template/angular2-product-app/src/main/frontend/src/tsconfig.json b/examples/demo-template/angular2-product-app/src/main/frontend/src/tsconfig.json
deleted file mode 100644
index 40fe5c0575..0000000000
--- a/examples/demo-template/angular2-product-app/src/main/frontend/src/tsconfig.json
+++ /dev/null
@@ -1,21 +0,0 @@
-{
- "compilerOptions": {
- "baseUrl": "",
- "declaration": false,
- "emitDecoratorMetadata": true,
- "experimentalDecorators": true,
- "lib": [
- "es2016",
- "dom"
- ],
- "mapRoot": "./",
- "module": "es2015",
- "moduleResolution": "node",
- "outDir": "../dist/out-tsc",
- "sourceMap": true,
- "target": "es5",
- "typeRoots": [
- "../node_modules/@types"
- ]
- }
-}
diff --git a/examples/demo-template/angular2-product-app/src/main/frontend/tslint.json b/examples/demo-template/angular2-product-app/src/main/frontend/tslint.json
deleted file mode 100644
index 9113f1368b..0000000000
--- a/examples/demo-template/angular2-product-app/src/main/frontend/tslint.json
+++ /dev/null
@@ -1,116 +0,0 @@
-{
- "rulesDirectory": [
- "node_modules/codelyzer"
- ],
- "rules": {
- "callable-types": true,
- "class-name": true,
- "comment-format": [
- true,
- "check-space"
- ],
- "curly": true,
- "eofline": true,
- "forin": true,
- "import-blacklist": [true, "rxjs"],
- "import-spacing": true,
- "indent": [
- true,
- "spaces"
- ],
- "interface-over-type-literal": true,
- "label-position": true,
- "max-line-length": [
- true,
- 140
- ],
- "member-access": false,
- "member-ordering": [
- true,
- "static-before-instance",
- "variables-before-functions"
- ],
- "no-arg": true,
- "no-bitwise": true,
- "no-console": [
- true,
- "debug",
- "info",
- "time",
- "timeEnd",
- "trace"
- ],
- "no-construct": true,
- "no-debugger": true,
- "no-duplicate-variable": true,
- "no-empty": false,
- "no-empty-interface": true,
- "no-eval": true,
- "no-inferrable-types": [true, "ignore-params"],
- "no-shadowed-variable": true,
- "no-string-literal": false,
- "no-string-throw": true,
- "no-switch-case-fall-through": true,
- "no-trailing-whitespace": true,
- "no-unused-expression": true,
- "no-use-before-declare": true,
- "no-var-keyword": true,
- "object-literal-sort-keys": false,
- "one-line": [
- true,
- "check-open-brace",
- "check-catch",
- "check-else",
- "check-whitespace"
- ],
- "prefer-const": true,
- "quotemark": [
- true,
- "single"
- ],
- "radix": true,
- "semicolon": [
- "always"
- ],
- "triple-equals": [
- true,
- "allow-null-check"
- ],
- "typedef-whitespace": [
- true,
- {
- "call-signature": "nospace",
- "index-signature": "nospace",
- "parameter": "nospace",
- "property-declaration": "nospace",
- "variable-declaration": "nospace"
- }
- ],
- "typeof-compare": true,
- "unified-signatures": true,
- "variable-name": false,
- "whitespace": [
- true,
- "check-branch",
- "check-decl",
- "check-operator",
- "check-separator",
- "check-type"
- ],
-
- "directive-selector": [true, "attribute", "app", "camelCase"],
- "component-selector": [true, "element", "app", "kebab-case"],
- "use-input-property-decorator": true,
- "use-output-property-decorator": true,
- "use-host-property-decorator": true,
- "no-input-rename": true,
- "no-output-rename": true,
- "use-life-cycle-interface": true,
- "use-pipe-transform-interface": true,
- "component-class-suffix": true,
- "directive-class-suffix": true,
- "no-access-missing-member": true,
- "templates-use-public": true,
- "invoke-injectable": true
- }
-}
diff --git a/examples/demo-template/pom.xml b/examples/demo-template/pom.xml
index a737bf86ea..bd239fb9cf 100755
--- a/examples/demo-template/pom.xml
+++ b/examples/demo-template/pom.xml
@@ -51,7 +51,6 @@
example-earadmin-access-appangular-product-app
- angular2-product-appdatabase-servicethird-partythird-party-cdi
diff --git a/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/KerberosFederationProviderFactory.java b/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/KerberosFederationProviderFactory.java
index e061f5eeb3..75e9d181bc 100755
--- a/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/KerberosFederationProviderFactory.java
+++ b/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/KerberosFederationProviderFactory.java
@@ -156,4 +156,9 @@ public class KerberosFederationProviderFactory implements UserStorageProviderFac
AuthenticationExecutionModel.Requirement.ALTERNATIVE, AuthenticationExecutionModel.Requirement.DISABLED);
}
+ @Override
+ public void preRemove(KeycloakSession session, RealmModel realm, ComponentModel model) {
+ CredentialHelper.setOrReplaceAuthenticationRequirement(session, realm, CredentialRepresentation.KERBEROS,
+ AuthenticationExecutionModel.Requirement.DISABLED, null);
+ }
}
diff --git a/federation/ldap/src/main/java/org/keycloak/storage/ldap/LDAPStorageProviderFactory.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/LDAPStorageProviderFactory.java
index c962af886d..0d4c07bdd5 100755
--- a/federation/ldap/src/main/java/org/keycloak/storage/ldap/LDAPStorageProviderFactory.java
+++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/LDAPStorageProviderFactory.java
@@ -384,8 +384,14 @@ public class LDAPStorageProviderFactory implements UserStorageProviderFactoryPedro Igor
+ */
+public interface GroupPoliciesResource {
+
+ @POST
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Produces(MediaType.APPLICATION_JSON)
+ Response create(GroupPolicyRepresentation representation);
+
+ @Path("{id}")
+ GroupPolicyResource findById(@PathParam("id") String id);
+
+ @Path("/search")
+ @GET
+ @Produces(MediaType.APPLICATION_JSON)
+ @NoCache
+ GroupPolicyRepresentation findByName(@QueryParam("name") String name);
+}
diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/GroupPolicyResource.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/GroupPolicyResource.java
new file mode 100644
index 0000000000..6171868b65
--- /dev/null
+++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/GroupPolicyResource.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2017 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.admin.client.resource;
+
+import java.util.List;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+
+import org.jboss.resteasy.annotations.cache.NoCache;
+import org.keycloak.representations.idm.authorization.GroupPolicyRepresentation;
+import org.keycloak.representations.idm.authorization.PolicyRepresentation;
+import org.keycloak.representations.idm.authorization.ResourceRepresentation;
+import org.keycloak.representations.idm.authorization.RolePolicyRepresentation;
+
+/**
+ * @author Pedro Igor
+ */
+public interface GroupPolicyResource {
+
+ @GET
+ @Produces(MediaType.APPLICATION_JSON)
+ @NoCache
+ GroupPolicyRepresentation toRepresentation();
+
+ @PUT
+ @Consumes(MediaType.APPLICATION_JSON)
+ void update(GroupPolicyRepresentation representation);
+
+ @DELETE
+ void remove();
+
+ @Path("/associatedPolicies")
+ @GET
+ @Produces(MediaType.APPLICATION_JSON)
+ @NoCache
+ List associatedPolicies();
+
+ @Path("/dependentPolicies")
+ @GET
+ @Produces(MediaType.APPLICATION_JSON)
+ @NoCache
+ List dependentPolicies();
+
+ @Path("/resources")
+ @GET
+ @Produces("application/json")
+ @NoCache
+ List resources();
+
+}
diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/PoliciesResource.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/PoliciesResource.java
index a0af5d4c62..9ced12c290 100644
--- a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/PoliciesResource.java
+++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/PoliciesResource.java
@@ -89,4 +89,7 @@ public interface PoliciesResource {
@Path("client")
ClientPoliciesResource client();
+
+ @Path("group")
+ GroupPoliciesResource group();
}
diff --git a/misc/CrossDataCenter.md b/misc/CrossDataCenter.md
index 4146eaa9e6..3313f3fe4b 100644
--- a/misc/CrossDataCenter.md
+++ b/misc/CrossDataCenter.md
@@ -3,6 +3,8 @@ Test Cross-Data-Center scenario (test with external JDG server)
These are temporary notes. This docs should be removed once we have cross-DC support finished and properly documented.
+Note that these steps are already automated, see Cross-DC tests section in [HOW-TO-RUN.md](../testsuite/integration-arquillian/HOW-TO-RUN.md) document.
+
What is working right now is:
- Propagating of invalidation messages for "realms" and "users" caches
- All the other things provided by ClusterProvider, which is:
@@ -18,7 +20,7 @@ Basic setup
This is setup with 2 keycloak nodes, which are NOT in cluster. They just share the same database and they will be configured with "work" infinispan cache with remoteStore, which will point
to external JDG server.
-
+
JDG Server setup
----------------
- Download JDG 7.0 server and unzip to some folder
diff --git a/model/infinispan/src/main/java/org/keycloak/connections/infinispan/DefaultInfinispanConnectionProviderFactory.java b/model/infinispan/src/main/java/org/keycloak/connections/infinispan/DefaultInfinispanConnectionProviderFactory.java
index 17ae1219fb..53e496f06c 100755
--- a/model/infinispan/src/main/java/org/keycloak/connections/infinispan/DefaultInfinispanConnectionProviderFactory.java
+++ b/model/infinispan/src/main/java/org/keycloak/connections/infinispan/DefaultInfinispanConnectionProviderFactory.java
@@ -154,9 +154,14 @@ public class DefaultInfinispanConnectionProviderFactory implements InfinispanCon
if (clustered) {
String nodeName = config.get("nodeName", System.getProperty(InfinispanConnectionProvider.JBOSS_NODE_NAME));
- configureTransport(gcb, nodeName);
+ String jgroupsUdpMcastAddr = config.get("jgroupsUdpMcastAddr", System.getProperty(InfinispanConnectionProvider.JGROUPS_UDP_MCAST_ADDR));
+ configureTransport(gcb, nodeName, jgroupsUdpMcastAddr);
+ gcb.globalJmxStatistics()
+ .jmxDomain(InfinispanConnectionProvider.JMX_DOMAIN + "-" + nodeName);
}
- gcb.globalJmxStatistics().allowDuplicateDomains(allowDuplicateJMXDomains);
+ gcb.globalJmxStatistics()
+ .allowDuplicateDomains(allowDuplicateJMXDomains)
+ .enable();
cacheManager = new DefaultCacheManager(gcb.build());
containerManaged = false;
@@ -317,24 +322,45 @@ public class DefaultInfinispanConnectionProviderFactory implements InfinispanCon
return cb.build();
}
- protected void configureTransport(GlobalConfigurationBuilder gcb, String nodeName) {
+ private static final Object CHANNEL_INIT_SYNCHRONIZER = new Object();
+
+ protected void configureTransport(GlobalConfigurationBuilder gcb, String nodeName, String jgroupsUdpMcastAddr) {
if (nodeName == null) {
gcb.transport().defaultTransport();
} else {
FileLookup fileLookup = FileLookupFactory.newInstance();
- try {
- // Compatibility with Wildfly
- JChannel channel = new JChannel(fileLookup.lookupFileLocation("default-configs/default-jgroups-udp.xml", this.getClass().getClassLoader()));
- channel.setName(nodeName);
- JGroupsTransport transport = new JGroupsTransport(channel);
+ synchronized (CHANNEL_INIT_SYNCHRONIZER) {
+ String originalMcastAddr = System.getProperty(InfinispanConnectionProvider.JGROUPS_UDP_MCAST_ADDR);
+ if (jgroupsUdpMcastAddr == null) {
+ System.getProperties().remove(InfinispanConnectionProvider.JGROUPS_UDP_MCAST_ADDR);
+ } else {
+ System.setProperty(InfinispanConnectionProvider.JGROUPS_UDP_MCAST_ADDR, jgroupsUdpMcastAddr);
+ }
+ try {
+ // Compatibility with Wildfly
+ JChannel channel = new JChannel(fileLookup.lookupFileLocation("default-configs/default-jgroups-udp.xml", this.getClass().getClassLoader()));
+ channel.setName(nodeName);
+ JGroupsTransport transport = new JGroupsTransport(channel);
- gcb.transport().nodeName(nodeName);
- gcb.transport().transport(transport);
+ gcb.transport()
+ .nodeName(nodeName)
+ .transport(transport)
+ .globalJmxStatistics()
+ .jmxDomain(InfinispanConnectionProvider.JMX_DOMAIN + "-" + nodeName)
+ .enable()
+ ;
- logger.infof("Configured jgroups transport with the channel name: %s", nodeName);
- } catch (Exception e) {
- throw new RuntimeException(e);
+ logger.infof("Configured jgroups transport with the channel name: %s", nodeName);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ } finally {
+ if (originalMcastAddr == null) {
+ System.getProperties().remove(InfinispanConnectionProvider.JGROUPS_UDP_MCAST_ADDR);
+ } else {
+ System.setProperty(InfinispanConnectionProvider.JGROUPS_UDP_MCAST_ADDR, originalMcastAddr);
+ }
+ }
}
}
}
diff --git a/model/infinispan/src/main/java/org/keycloak/connections/infinispan/InfinispanConnectionProvider.java b/model/infinispan/src/main/java/org/keycloak/connections/infinispan/InfinispanConnectionProvider.java
index 8618a699ec..e8cdbf6885 100755
--- a/model/infinispan/src/main/java/org/keycloak/connections/infinispan/InfinispanConnectionProvider.java
+++ b/model/infinispan/src/main/java/org/keycloak/connections/infinispan/InfinispanConnectionProvider.java
@@ -53,7 +53,9 @@ public interface InfinispanConnectionProvider extends Provider {
// System property used on Wildfly to identify distributedCache address and sticky session route
String JBOSS_NODE_NAME = "jboss.node.name";
+ String JGROUPS_UDP_MCAST_ADDR = "jgroups.udp.mcast_addr";
+ String JMX_DOMAIN = "jboss.datagrid-infinispan";
Cache getCache(String name);
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/CacheManager.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/CacheManager.java
index c9832ff558..4480f7af9a 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/CacheManager.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/CacheManager.java
@@ -220,7 +220,7 @@ public abstract class CacheManager {
addInvalidationsFromEvent(event, invalidations);
- getLogger().debugf("Invalidating %d cache items after received event %s", invalidations.size(), event);
+ getLogger().debugf("[%s] Invalidating %d cache items after received event %s", cache.getCacheManager().getAddress(), invalidations.size(), event);
for (String invalidation : invalidations) {
invalidateObject(invalidation);
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmCacheSession.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmCacheSession.java
index 6c654238bf..f9bf0d7e50 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmCacheSession.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmCacheSession.java
@@ -19,6 +19,8 @@ package org.keycloak.models.cache.infinispan;
import org.jboss.logging.Logger;
import org.keycloak.cluster.ClusterProvider;
+import org.keycloak.models.ClientInitialAccessModel;
+import org.keycloak.models.cache.infinispan.events.InvalidationEvent;
import org.keycloak.migration.MigrationModel;
import org.keycloak.models.*;
import org.keycloak.models.cache.CacheRealmProvider;
@@ -129,9 +131,8 @@ public class RealmCacheSession implements CacheRealmProvider {
@Override
public void clear() {
- cache.clear();
ClusterProvider cluster = session.getProvider(ClusterProvider.class);
- cluster.notify(InfinispanCacheRealmProviderFactory.REALM_CLEAR_CACHE_EVENTS, new ClearCacheEvent(), true);
+ cluster.notify(InfinispanCacheRealmProviderFactory.REALM_CLEAR_CACHE_EVENTS, new ClearCacheEvent(), false);
}
@Override
@@ -1076,4 +1077,34 @@ public class RealmCacheSession implements CacheRealmProvider {
return adapter;
}
+ // Don't cache ClientInitialAccessModel for now
+ @Override
+ public ClientInitialAccessModel createClientInitialAccessModel(RealmModel realm, int expiration, int count) {
+ return getDelegate().createClientInitialAccessModel(realm, expiration, count);
+ }
+
+ @Override
+ public ClientInitialAccessModel getClientInitialAccessModel(RealmModel realm, String id) {
+ return getDelegate().getClientInitialAccessModel(realm, id);
+ }
+
+ @Override
+ public void removeClientInitialAccessModel(RealmModel realm, String id) {
+ getDelegate().removeClientInitialAccessModel(realm, id);
+ }
+
+ @Override
+ public List listClientInitialAccess(RealmModel realm) {
+ return getDelegate().listClientInitialAccess(realm);
+ }
+
+ @Override
+ public void removeExpiredClientInitialAccess() {
+ getDelegate().removeExpiredClientInitialAccess();
+ }
+
+ @Override
+ public void decreaseRemainingCount(RealmModel realm, ClientInitialAccessModel clientInitialAccess) {
+ getDelegate().decreaseRemainingCount(realm, clientInitialAccess);
+ }
}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/InfinispanCacheStoreFactoryProviderFactory.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/InfinispanCacheStoreFactoryProviderFactory.java
index c5dc7fc23a..c74e4fedf7 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/InfinispanCacheStoreFactoryProviderFactory.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/InfinispanCacheStoreFactoryProviderFactory.java
@@ -17,21 +17,19 @@
package org.keycloak.models.cache.infinispan.authorization;
+import static org.keycloak.models.cache.infinispan.InfinispanCacheRealmProviderFactory.REALM_CLEAR_CACHE_EVENTS;
+
import org.infinispan.Cache;
import org.jboss.logging.Logger;
import org.keycloak.Config;
-import org.keycloak.authorization.AuthorizationProvider;
import org.keycloak.cluster.ClusterEvent;
import org.keycloak.cluster.ClusterProvider;
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
-import org.keycloak.models.cache.CacheRealmProvider;
-import org.keycloak.models.cache.CacheRealmProviderFactory;
import org.keycloak.models.cache.authorization.CachedStoreFactoryProvider;
import org.keycloak.models.cache.authorization.CachedStoreProviderFactory;
-import org.keycloak.models.cache.infinispan.RealmCacheManager;
-import org.keycloak.models.cache.infinispan.RealmCacheSession;
+import org.keycloak.models.cache.infinispan.ClearCacheEvent;
import org.keycloak.models.cache.infinispan.entities.Revisioned;
import org.keycloak.models.cache.infinispan.events.InvalidationEvent;
@@ -59,21 +57,17 @@ public class InfinispanCacheStoreFactoryProviderFactory implements CachedStorePr
Cache cache = session.getProvider(InfinispanConnectionProvider.class).getCache(InfinispanConnectionProvider.AUTHORIZATION_CACHE_NAME);
Cache revisions = session.getProvider(InfinispanConnectionProvider.class).getCache(InfinispanConnectionProvider.AUTHORIZATION_REVISIONS_CACHE_NAME);
storeCache = new StoreFactoryCacheManager(cache, revisions);
-
ClusterProvider cluster = session.getProvider(ClusterProvider.class);
- cluster.registerListener(ClusterProvider.ALL, (ClusterEvent event) -> {
+ cluster.registerListener(ClusterProvider.ALL, (ClusterEvent event) -> {
if (event instanceof InvalidationEvent) {
InvalidationEvent invalidationEvent = (InvalidationEvent) event;
storeCache.invalidationEventReceived(invalidationEvent);
}
});
- cluster.registerListener(AUTHORIZATION_CLEAR_CACHE_EVENTS, (ClusterEvent event) -> {
-
- storeCache.clear();
-
- });
+ cluster.registerListener(AUTHORIZATION_CLEAR_CACHE_EVENTS, (ClusterEvent event) -> storeCache.clear());
+ cluster.registerListener(REALM_CLEAR_CACHE_EVENTS, (ClusterEvent event) -> storeCache.clear());
log.debug("Registered cluster listeners");
}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/PolicyAdapter.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/PolicyAdapter.java
index 970d1b9e34..7660c96d1e 100644
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/PolicyAdapter.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/PolicyAdapter.java
@@ -47,7 +47,7 @@ public class PolicyAdapter implements Policy, CachedModel {
@Override
public Policy getDelegateForUpdate() {
if (updated == null) {
- cacheSession.registerPolicyInvalidation(cached.getId(), cached.getName(), cached.getResourcesIds(), cached.getResourceServerId());
+ cacheSession.registerPolicyInvalidation(cached.getId(), cached.getName(), cached.getResourcesIds(), cached.getScopesIds(), cached.getResourceServerId());
updated = cacheSession.getPolicyStoreDelegate().findById(cached.getId(), cached.getResourceServerId());
if (updated == null) throw new IllegalStateException("Not found in database");
}
@@ -96,7 +96,7 @@ public class PolicyAdapter implements Policy, CachedModel {
@Override
public void setName(String name) {
getDelegateForUpdate();
- cacheSession.registerPolicyInvalidation(cached.getId(), name, cached.getResourcesIds(), cached.getResourceServerId());
+ cacheSession.registerPolicyInvalidation(cached.getId(), name, cached.getResourcesIds(), cached.getScopesIds(), cached.getResourceServerId());
updated.setName(name);
}
@@ -235,7 +235,7 @@ public class PolicyAdapter implements Policy, CachedModel {
getDelegateForUpdate();
HashSet resources = new HashSet<>();
resources.add(resource.getId());
- cacheSession.registerPolicyInvalidation(cached.getId(), cached.getName(), resources, cached.getResourceServerId());
+ cacheSession.registerPolicyInvalidation(cached.getId(), cached.getName(), resources, cached.getScopesIds(), cached.getResourceServerId());
updated.addResource(resource);
}
@@ -245,7 +245,7 @@ public class PolicyAdapter implements Policy, CachedModel {
getDelegateForUpdate();
HashSet resources = new HashSet<>();
resources.add(resource.getId());
- cacheSession.registerPolicyInvalidation(cached.getId(), cached.getName(), resources, cached.getResourceServerId());
+ cacheSession.registerPolicyInvalidation(cached.getId(), cached.getName(), resources, cached.getScopesIds(), cached.getResourceServerId());
updated.removeResource(resource);
}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/StoreFactoryCacheManager.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/StoreFactoryCacheManager.java
index 794358926f..63eb8a73da 100644
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/StoreFactoryCacheManager.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/StoreFactoryCacheManager.java
@@ -102,7 +102,7 @@ public class StoreFactoryCacheManager extends CacheManager {
addInvalidations(InResourcePredicate.create().resource(id), invalidations);
}
- public void policyUpdated(String id, String name, Set resources, String serverId, Set invalidations) {
+ public void policyUpdated(String id, String name, Set resources, Set resourceTypes, Set scopes, String serverId, Set invalidations) {
invalidations.add(id);
invalidations.add(StoreFactoryCacheSession.getPolicyByNameCacheKey(name, serverId));
@@ -111,10 +111,22 @@ public class StoreFactoryCacheManager extends CacheManager {
invalidations.add(StoreFactoryCacheSession.getPolicyByResource(resource, serverId));
}
}
+
+ if (resourceTypes != null) {
+ for (String type : resourceTypes) {
+ invalidations.add(StoreFactoryCacheSession.getPolicyByResourceType(type, serverId));
+ }
+ }
+
+ if (scopes != null) {
+ for (String scope : scopes) {
+ invalidations.add(StoreFactoryCacheSession.getPolicyByScope(scope, serverId));
+ }
+ }
}
- public void policyRemoval(String id, String name, Set resources, String serverId, Set invalidations) {
- policyUpdated(id, name, resources, serverId, invalidations);
+ public void policyRemoval(String id, String name, Set resources, Set resourceTypes, Set scopes, String serverId, Set invalidations) {
+ policyUpdated(id, name, resources, resourceTypes, scopes, serverId, invalidations);
}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/StoreFactoryCacheSession.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/StoreFactoryCacheSession.java
index 3e4c2052b2..10be78d971 100644
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/StoreFactoryCacheSession.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/StoreFactoryCacheSession.java
@@ -23,6 +23,7 @@ import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.function.Supplier;
@@ -252,12 +253,30 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider {
invalidationEvents.add(ResourceUpdatedEvent.create(id, name, type, uri, scopes, serverId));
}
- public void registerPolicyInvalidation(String id, String name, Set resources, String serverId) {
- cache.policyUpdated(id, name, resources, serverId, invalidations);
+ public void registerPolicyInvalidation(String id, String name, Set resources, Set scopes, String serverId) {
+ Set resourceTypes = getResourceTypes(resources, serverId);
+ cache.policyUpdated(id, name, resources, resourceTypes, scopes, serverId, invalidations);
PolicyAdapter adapter = managedPolicies.get(id);
if (adapter != null) adapter.invalidateFlag();
- invalidationEvents.add(PolicyUpdatedEvent.create(id, name, resources, serverId));
+ invalidationEvents.add(PolicyUpdatedEvent.create(id, name, resources, resourceTypes, scopes, serverId));
+ }
+
+ private Set getResourceTypes(Set resources, String serverId) {
+ if (resources == null) {
+ return Collections.emptySet();
+ }
+
+ return resources.stream().map(resourceId -> {
+ Resource resource = getResourceStore().findById(resourceId, serverId);
+ String type = resource.getType();
+
+ if (type != null) {
+ return type;
+ }
+
+ return null;
+ }).filter(Objects::nonNull).collect(Collectors.toSet());
}
public ResourceServerStore getResourceServerStoreDelegate() {
@@ -626,7 +645,7 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider {
@Override
public Policy create(AbstractPolicyRepresentation representation, ResourceServer resourceServer) {
Policy policy = getPolicyStoreDelegate().create(representation, resourceServer);
- registerPolicyInvalidation(policy.getId(), policy.getName(), policy.getResources().stream().map(resource1 -> resource1.getId()).collect(Collectors.toSet()), resourceServer.getId());
+ registerPolicyInvalidation(policy.getId(), representation.getName(), representation.getResources(), representation.getScopes(), resourceServer.getId());
return policy;
}
@@ -637,8 +656,12 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider {
if (policy == null) return;
cache.invalidateObject(id);
- invalidationEvents.add(PolicyRemovedEvent.create(id, policy.getName(), policy.getResources().stream().map(resource1 -> resource1.getId()).collect(Collectors.toSet()), policy.getResourceServer().getId()));
- cache.policyRemoval(id, policy.getName(), policy.getResources().stream().map(resource1 -> resource1.getId()).collect(Collectors.toSet()), policy.getResourceServer().getId(), invalidations);
+ Set resources = policy.getResources().stream().map(resource -> resource.getId()).collect(Collectors.toSet());
+ ResourceServer resourceServer = policy.getResourceServer();
+ Set resourceTypes = getResourceTypes(resources, resourceServer.getId());
+ Set scopes = policy.getScopes().stream().map(scope -> scope.getId()).collect(Collectors.toSet());
+ invalidationEvents.add(PolicyRemovedEvent.create(id, policy.getName(), resources, resourceTypes, scopes, resourceServer.getId()));
+ cache.policyRemoval(id, policy.getName(), resources, resourceTypes, scopes, resourceServer.getId(), invalidations);
getPolicyStoreDelegate().delete(id);
}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/events/PolicyRemovedEvent.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/events/PolicyRemovedEvent.java
index 759c284b93..4d46f8189e 100644
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/events/PolicyRemovedEvent.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/events/PolicyRemovedEvent.java
@@ -30,13 +30,17 @@ public class PolicyRemovedEvent extends InvalidationEvent implements Authorizati
private String id;
private String name;
private Set resources;
+ private Set resourceTypes;
+ private Set scopes;
private String serverId;
- public static PolicyRemovedEvent create(String id, String name, Set resources, String serverId) {
+ public static PolicyRemovedEvent create(String id, String name, Set resources, Set resourceTypes, Set scopes, String serverId) {
PolicyRemovedEvent event = new PolicyRemovedEvent();
event.id = id;
event.name = name;
event.resources = resources;
+ event.resourceTypes = resourceTypes;
+ event.scopes = scopes;
event.serverId = serverId;
return event;
}
@@ -53,6 +57,6 @@ public class PolicyRemovedEvent extends InvalidationEvent implements Authorizati
@Override
public void addInvalidations(StoreFactoryCacheManager cache, Set invalidations) {
- cache.policyRemoval(id, name, resources, serverId, invalidations);
+ cache.policyRemoval(id, name, resources, resourceTypes, scopes, serverId, invalidations);
}
}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/events/PolicyUpdatedEvent.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/events/PolicyUpdatedEvent.java
index b576bda165..d613c57dce 100644
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/events/PolicyUpdatedEvent.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/events/PolicyUpdatedEvent.java
@@ -30,13 +30,17 @@ public class PolicyUpdatedEvent extends InvalidationEvent implements Authorizati
private String id;
private String name;
private static Set resources;
+ private Set resourceTypes;
+ private Set scopes;
private String serverId;
- public static PolicyUpdatedEvent create(String id, String name, Set resources, String serverId) {
+ public static PolicyUpdatedEvent create(String id, String name, Set resources, Set resourceTypes, Set scopes, String serverId) {
PolicyUpdatedEvent event = new PolicyUpdatedEvent();
event.id = id;
event.name = name;
event.resources = resources;
+ event.resourceTypes = resourceTypes;
+ event.scopes = scopes;
event.serverId = serverId;
return event;
}
@@ -53,6 +57,6 @@ public class PolicyUpdatedEvent extends InvalidationEvent implements Authorizati
@Override
public void addInvalidations(StoreFactoryCacheManager cache, Set invalidations) {
- cache.policyUpdated(id, name, resources, serverId, invalidations);
+ cache.policyUpdated(id, name, resources, resourceTypes, scopes, serverId, invalidations);
}
}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/AddInvalidatedActionTokenEvent.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/events/AddInvalidatedActionTokenEvent.java
similarity index 96%
rename from model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/AddInvalidatedActionTokenEvent.java
rename to model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/events/AddInvalidatedActionTokenEvent.java
index 37a1a218d5..2bced7e451 100644
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/AddInvalidatedActionTokenEvent.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/events/AddInvalidatedActionTokenEvent.java
@@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.keycloak.models.cache.infinispan;
+package org.keycloak.models.cache.infinispan.events;
import org.keycloak.cluster.ClusterEvent;
import org.keycloak.models.sessions.infinispan.entities.ActionTokenReducedKey;
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RemoveActionTokensSpecificEvent.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/events/RemoveActionTokensSpecificEvent.java
similarity index 95%
rename from model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RemoveActionTokensSpecificEvent.java
rename to model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/events/RemoveActionTokensSpecificEvent.java
index 0a4d858b52..d658f30c58 100644
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RemoveActionTokensSpecificEvent.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/events/RemoveActionTokensSpecificEvent.java
@@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.keycloak.models.cache.infinispan;
+package org.keycloak.models.cache.infinispan.events;
import org.keycloak.cluster.ClusterEvent;
diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/ClientInitialAccessAdapter.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/ClientInitialAccessAdapter.java
deleted file mode 100644
index 179d8dc900..0000000000
--- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/ClientInitialAccessAdapter.java
+++ /dev/null
@@ -1,86 +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.models.sessions.infinispan;
-
-import org.infinispan.Cache;
-import org.keycloak.models.ClientInitialAccessModel;
-import org.keycloak.models.KeycloakSession;
-import org.keycloak.models.RealmModel;
-import org.keycloak.models.sessions.infinispan.entities.ClientInitialAccessEntity;
-import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
-
-/**
- * @author Stian Thorgersen
- */
-public class ClientInitialAccessAdapter implements ClientInitialAccessModel {
-
- private final KeycloakSession session;
- private final InfinispanUserSessionProvider provider;
- private final Cache cache;
- private final RealmModel realm;
- private final ClientInitialAccessEntity entity;
-
- public ClientInitialAccessAdapter(KeycloakSession session, InfinispanUserSessionProvider provider, Cache cache, RealmModel realm, ClientInitialAccessEntity entity) {
- this.session = session;
- this.provider = provider;
- this.cache = cache;
- this.realm = realm;
- this.entity = entity;
- }
-
- @Override
- public String getId() {
- return entity.getId();
- }
-
- @Override
- public RealmModel getRealm() {
- return realm;
- }
-
- @Override
- public int getTimestamp() {
- return entity.getTimestamp();
- }
-
- @Override
- public int getExpiration() {
- return entity.getExpiration();
- }
-
- @Override
- public int getCount() {
- return entity.getCount();
- }
-
- @Override
- public int getRemainingCount() {
- return entity.getRemainingCount();
- }
-
- @Override
- public void decreaseRemainingCount() {
- entity.setRemainingCount(entity.getRemainingCount() - 1);
- update();
- }
-
- void update() {
- provider.getTx().replace(cache, entity.getId(), entity);
- }
-
-}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanActionTokenStoreProvider.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanActionTokenStoreProvider.java
index 127879a4d1..f02fb5a403 100644
--- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanActionTokenStoreProvider.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanActionTokenStoreProvider.java
@@ -19,8 +19,8 @@ package org.keycloak.models.sessions.infinispan;
import org.keycloak.cluster.ClusterProvider;
import org.keycloak.models.*;
-import org.keycloak.models.cache.infinispan.AddInvalidatedActionTokenEvent;
-import org.keycloak.models.cache.infinispan.RemoveActionTokensSpecificEvent;
+import org.keycloak.models.cache.infinispan.events.AddInvalidatedActionTokenEvent;
+import org.keycloak.models.cache.infinispan.events.RemoveActionTokensSpecificEvent;
import org.keycloak.models.sessions.infinispan.entities.ActionTokenValueEntity;
import org.keycloak.models.sessions.infinispan.entities.ActionTokenReducedKey;
import java.util.*;
@@ -58,7 +58,12 @@ public class InfinispanActionTokenStoreProvider implements ActionTokenStoreProvi
ActionTokenValueEntity tokenValue = new ActionTokenValueEntity(notes);
ClusterProvider cluster = session.getProvider(ClusterProvider.class);
- this.tx.notify(cluster, InfinispanActionTokenStoreProviderFactory.ACTION_TOKEN_EVENTS, new AddInvalidatedActionTokenEvent(tokenKey, key.getExpiration(), tokenValue), false);
+ AddInvalidatedActionTokenEvent event = new AddInvalidatedActionTokenEvent(tokenKey, key.getExpiration(), tokenValue);
+ this.tx.notify(cluster, generateActionTokenEventId(), event, false);
+ }
+
+ private static String generateActionTokenEventId() {
+ return InfinispanActionTokenStoreProviderFactory.ACTION_TOKEN_EVENTS + "/" + UUID.randomUUID();
}
@Override
@@ -93,6 +98,6 @@ public class InfinispanActionTokenStoreProvider implements ActionTokenStoreProvi
}
ClusterProvider cluster = session.getProvider(ClusterProvider.class);
- this.tx.notify(cluster, InfinispanActionTokenStoreProviderFactory.ACTION_TOKEN_EVENTS, new RemoveActionTokensSpecificEvent(userId, actionId), false);
+ this.tx.notify(cluster, generateActionTokenEventId(), new RemoveActionTokensSpecificEvent(userId, actionId), false);
}
}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanActionTokenStoreProviderFactory.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanActionTokenStoreProviderFactory.java
index a8c5e3899e..f67f28f6c0 100644
--- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanActionTokenStoreProviderFactory.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanActionTokenStoreProviderFactory.java
@@ -23,14 +23,16 @@ import org.keycloak.common.util.Time;
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
import org.keycloak.models.*;
-import org.keycloak.models.cache.infinispan.AddInvalidatedActionTokenEvent;
-import org.keycloak.models.cache.infinispan.RemoveActionTokensSpecificEvent;
+import org.keycloak.models.cache.infinispan.events.AddInvalidatedActionTokenEvent;
+import org.keycloak.models.cache.infinispan.events.RemoveActionTokensSpecificEvent;
import org.keycloak.models.sessions.infinispan.entities.ActionTokenValueEntity;
import org.keycloak.models.sessions.infinispan.entities.ActionTokenReducedKey;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import org.infinispan.Cache;
import org.infinispan.context.Flag;
+import org.infinispan.remoting.transport.Address;
+import org.jboss.logging.Logger;
/**
*
@@ -38,6 +40,10 @@ import org.infinispan.context.Flag;
*/
public class InfinispanActionTokenStoreProviderFactory implements ActionTokenStoreProviderFactory {
+ private static final Logger LOG = Logger.getLogger(InfinispanActionTokenStoreProviderFactory.class);
+
+ private volatile Cache actionTokenCache;
+
public static final String ACTION_TOKEN_EVENTS = "ACTION_TOKEN_EVENTS";
/**
@@ -49,34 +55,7 @@ public class InfinispanActionTokenStoreProviderFactory implements ActionTokenSto
@Override
public ActionTokenStoreProvider create(KeycloakSession session) {
- InfinispanConnectionProvider connections = session.getProvider(InfinispanConnectionProvider.class);
- Cache actionTokenCache = connections.getCache(InfinispanConnectionProvider.ACTION_TOKEN_CACHE);
-
- ClusterProvider cluster = session.getProvider(ClusterProvider.class);
-
- cluster.registerListener(ACTION_TOKEN_EVENTS, event -> {
- if (event instanceof RemoveActionTokensSpecificEvent) {
- RemoveActionTokensSpecificEvent e = (RemoveActionTokensSpecificEvent) event;
-
- actionTokenCache
- .getAdvancedCache()
- .withFlags(Flag.CACHE_MODE_LOCAL, Flag.SKIP_CACHE_LOAD)
- .keySet()
- .stream()
- .filter(k -> Objects.equals(k.getUserId(), e.getUserId()) && Objects.equals(k.getActionId(), e.getActionId()))
- .forEach(actionTokenCache::remove);
- } else if (event instanceof AddInvalidatedActionTokenEvent) {
- AddInvalidatedActionTokenEvent e = (AddInvalidatedActionTokenEvent) event;
-
- if (e.getExpirationInSecs() == DEFAULT_CACHE_EXPIRATION) {
- actionTokenCache.put(e.getKey(), e.getTokenValue());
- } else {
- actionTokenCache.put(e.getKey(), e.getTokenValue(), e.getExpirationInSecs() - Time.currentTime(), TimeUnit.SECONDS);
- }
- }
- });
-
- return new InfinispanActionTokenStoreProvider(session, actionTokenCache);
+ return new InfinispanActionTokenStoreProvider(session, this.actionTokenCache);
}
@Override
@@ -84,8 +63,57 @@ public class InfinispanActionTokenStoreProviderFactory implements ActionTokenSto
this.config = config;
}
+ private static Cache initActionTokenCache(KeycloakSession session) {
+ InfinispanConnectionProvider connections = session.getProvider(InfinispanConnectionProvider.class);
+ Cache cache = connections.getCache(InfinispanConnectionProvider.ACTION_TOKEN_CACHE);
+ final Address cacheAddress = cache.getCacheManager().getAddress();
+
+ ClusterProvider cluster = session.getProvider(ClusterProvider.class);
+
+ cluster.registerListener(ClusterProvider.ALL, event -> {
+ if (event instanceof RemoveActionTokensSpecificEvent) {
+ RemoveActionTokensSpecificEvent e = (RemoveActionTokensSpecificEvent) event;
+
+ LOG.debugf("[%s] Removing token invalidation for user+action: userId=%s, actionId=%s", cacheAddress, e.getUserId(), e.getActionId());
+
+ cache
+ .getAdvancedCache()
+ .withFlags(Flag.CACHE_MODE_LOCAL, Flag.SKIP_CACHE_LOAD)
+ .keySet()
+ .stream()
+ .filter(k -> Objects.equals(k.getUserId(), e.getUserId()) && Objects.equals(k.getActionId(), e.getActionId()))
+ .forEach(cache::remove);
+ } else if (event instanceof AddInvalidatedActionTokenEvent) {
+ AddInvalidatedActionTokenEvent e = (AddInvalidatedActionTokenEvent) event;
+
+ LOG.debugf("[%s] Invalidating token %s", cacheAddress, e.getKey());
+ if (e.getExpirationInSecs() == DEFAULT_CACHE_EXPIRATION) {
+ cache.put(e.getKey(), e.getTokenValue());
+ } else {
+ cache.put(e.getKey(), e.getTokenValue(), e.getExpirationInSecs() - Time.currentTime(), TimeUnit.SECONDS);
+ }
+ }
+ });
+
+ LOG.debugf("[%s] Registered cluster listeners", cacheAddress);
+
+ return cache;
+ }
+
@Override
public void postInit(KeycloakSessionFactory factory) {
+ Cache cache = this.actionTokenCache;
+
+ // It is necessary to put the cache initialization here, otherwise the cache would be initialized lazily, that
+ // means also listeners will start only after first cache initialization - that would be too late
+ if (cache == null) {
+ synchronized (this) {
+ cache = this.actionTokenCache;
+ if (cache == null) {
+ this.actionTokenCache = initActionTokenCache(factory.create());
+ }
+ }
+ }
}
@Override
diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanAuthenticationSessionProviderFactory.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanAuthenticationSessionProviderFactory.java
index 83e970ddaa..a9589ccca5 100644
--- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanAuthenticationSessionProviderFactory.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanAuthenticationSessionProviderFactory.java
@@ -92,7 +92,7 @@ public class InfinispanAuthenticationSessionProviderFactory implements Authentic
ClusterProvider cluster = session.getProvider(ClusterProvider.class);
cluster.registerListener(AUTHENTICATION_SESSION_EVENTS, this::updateAuthNotes);
- log.debug("Registered cluster listeners");
+ log.debugf("[%s] Registered cluster listeners", authSessionsCache.getCacheManager().getAddress());
}
}
}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java
index 0476698719..202a051f14 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java
@@ -22,7 +22,6 @@ import org.infinispan.CacheStream;
import org.infinispan.context.Flag;
import org.jboss.logging.Logger;
import org.keycloak.common.util.Time;
-import org.keycloak.models.ClientInitialAccessModel;
import org.keycloak.models.AuthenticatedClientSessionModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
@@ -32,23 +31,19 @@ import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.models.UserSessionProvider;
import org.keycloak.models.session.UserSessionPersisterProvider;
-import org.keycloak.models.sessions.infinispan.entities.ClientInitialAccessEntity;
import org.keycloak.models.sessions.infinispan.entities.AuthenticatedClientSessionEntity;
import org.keycloak.models.sessions.infinispan.entities.LoginFailureEntity;
import org.keycloak.models.sessions.infinispan.entities.LoginFailureKey;
import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity;
-import org.keycloak.models.sessions.infinispan.stream.ClientInitialAccessPredicate;
import org.keycloak.models.sessions.infinispan.stream.Comparators;
import org.keycloak.models.sessions.infinispan.stream.Mappers;
import org.keycloak.models.sessions.infinispan.stream.SessionPredicate;
import org.keycloak.models.sessions.infinispan.stream.UserLoginFailurePredicate;
import org.keycloak.models.sessions.infinispan.stream.UserSessionPredicate;
-import org.keycloak.models.utils.KeycloakModelUtils;
import java.util.Collection;
import java.util.Collections;
-import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
@@ -271,7 +266,6 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
log.debugf("Removing expired sessions");
removeExpiredUserSessions(realm);
removeExpiredOfflineUserSessions(realm);
- removeExpiredClientInitialAccess(realm);
}
private void removeExpiredUserSessions(RealmModel realm) {
@@ -317,14 +311,6 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
log.debugf("Removed %d expired offline user sessions for realm '%s'", counter, realm.getName());
}
- private void removeExpiredClientInitialAccess(RealmModel realm) {
- Iterator itr = sessionCache.getAdvancedCache().withFlags(Flag.CACHE_MODE_LOCAL)
- .entrySet().stream().filter(ClientInitialAccessPredicate.create(realm.getId()).expired(Time.currentTime())).map(Mappers.sessionId()).iterator();
- while (itr.hasNext()) {
- tx.remove(sessionCache, itr.next());
- }
- }
-
@Override
public void removeUserSessions(RealmModel realm) {
removeUserSessions(realm, false);
@@ -417,19 +403,6 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
return models;
}
- List wrapClientInitialAccess(RealmModel realm, Collection entities) {
- List models = new LinkedList<>();
- for (ClientInitialAccessEntity e : entities) {
- models.add(wrap(realm, e));
- }
- return models;
- }
-
- ClientInitialAccessAdapter wrap(RealmModel realm, ClientInitialAccessEntity entity) {
- Cache cache = getCache(false);
- return entity != null ? new ClientInitialAccessAdapter(session, this, cache, realm, entity) : null;
- }
-
UserLoginFailureModel wrap(LoginFailureKey key, LoginFailureEntity entity) {
return entity != null ? new UserLoginFailureAdapter(this, loginFailureCache, key, entity) : null;
}
@@ -565,48 +538,4 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
return new AuthenticatedClientSessionAdapter(entity, clientSession.getClient(), importedUserSession, this, importedUserSession.getCache());
}
- @Override
- public ClientInitialAccessModel createClientInitialAccessModel(RealmModel realm, int expiration, int count) {
- String id = KeycloakModelUtils.generateId();
-
- ClientInitialAccessEntity entity = new ClientInitialAccessEntity();
- entity.setId(id);
- entity.setRealm(realm.getId());
- entity.setTimestamp(Time.currentTime());
- entity.setExpiration(expiration);
- entity.setCount(count);
- entity.setRemainingCount(count);
-
- tx.put(sessionCache, id, entity);
-
- return wrap(realm, entity);
- }
-
- @Override
- public ClientInitialAccessModel getClientInitialAccessModel(RealmModel realm, String id) {
- Cache cache = getCache(false);
- ClientInitialAccessEntity entity = (ClientInitialAccessEntity) tx.get(cache, id); // Chance created in this transaction
-
- if (entity == null) {
- entity = (ClientInitialAccessEntity) cache.get(id);
- }
-
- return wrap(realm, entity);
- }
-
- @Override
- public void removeClientInitialAccessModel(RealmModel realm, String id) {
- tx.remove(getCache(false), id);
- }
-
- @Override
- public List listClientInitialAccess(RealmModel realm) {
- Iterator> itr = sessionCache.entrySet().stream().filter(ClientInitialAccessPredicate.create(realm.getId())).iterator();
- List list = new LinkedList<>();
- while (itr.hasNext()) {
- list.add(wrap(realm, (ClientInitialAccessEntity) itr.next().getValue()));
- }
- return list;
- }
-
}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/ActionTokenReducedKey.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/ActionTokenReducedKey.java
index 173c43497d..24742e58d6 100644
--- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/ActionTokenReducedKey.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/ActionTokenReducedKey.java
@@ -81,6 +81,11 @@ public class ActionTokenReducedKey implements Serializable {
&& Objects.equals(this.actionVerificationNonce, other.getActionVerificationNonce());
}
+ @Override
+ public String toString() {
+ return "userId=" + userId + ", actionId=" + actionId + ", actionVerificationNonce=" + actionVerificationNonce;
+ }
+
public static class ExternalizerImpl implements Externalizer {
@Override
diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/ActionTokenValueEntity.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/ActionTokenValueEntity.java
index 7c0f663da6..7e3c76ed5e 100644
--- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/ActionTokenValueEntity.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/ActionTokenValueEntity.java
@@ -53,7 +53,7 @@ public class ActionTokenValueEntity implements ActionTokenValueModel {
public void writeObject(ObjectOutput output, ActionTokenValueEntity t) throws IOException {
output.writeByte(VERSION_1);
- output.writeBoolean(! t.notes.isEmpty());
+ output.writeBoolean(t.notes.isEmpty());
if (! t.notes.isEmpty()) {
output.writeObject(t.notes);
}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/ClientInitialAccessEntity.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/ClientInitialAccessEntity.java
deleted file mode 100644
index 20d583d282..0000000000
--- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/ClientInitialAccessEntity.java
+++ /dev/null
@@ -1,65 +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.models.sessions.infinispan.entities;
-
-/**
- * @author Stian Thorgersen
- */
-public class ClientInitialAccessEntity extends SessionEntity {
-
- private int timestamp;
-
- private int expires;
-
- private int count;
-
- private int remainingCount;
-
- public int getTimestamp() {
- return timestamp;
- }
-
- public void setTimestamp(int timestamp) {
- this.timestamp = timestamp;
- }
-
- public int getExpiration() {
- return expires;
- }
-
- public void setExpiration(int expires) {
- this.expires = expires;
- }
-
- public int getCount() {
- return count;
- }
-
- public void setCount(int count) {
- this.count = count;
- }
-
- public int getRemainingCount() {
- return remainingCount;
- }
-
- public void setRemainingCount(int remainingCount) {
- this.remainingCount = remainingCount;
- }
-
-}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/mapreduce/ClientInitialAccessMapper.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/mapreduce/ClientInitialAccessMapper.java
deleted file mode 100644
index 9cfb3b4590..0000000000
--- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/mapreduce/ClientInitialAccessMapper.java
+++ /dev/null
@@ -1,96 +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.models.sessions.infinispan.mapreduce;
-
-import org.infinispan.distexec.mapreduce.Collector;
-import org.infinispan.distexec.mapreduce.Mapper;
-import org.keycloak.models.sessions.infinispan.entities.ClientInitialAccessEntity;
-import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
-
-import java.io.Serializable;
-
-/**
- * @author Stian Thorgersen
- */
-public class ClientInitialAccessMapper implements Mapper, Serializable {
-
- public ClientInitialAccessMapper(String realm) {
- this.realm = realm;
- }
-
- private enum EmitValue {
- KEY, ENTITY
- }
-
- private String realm;
-
- private EmitValue emit = EmitValue.ENTITY;
-
- private Integer expired;
-
- public static ClientInitialAccessMapper create(String realm) {
- return new ClientInitialAccessMapper(realm);
- }
-
- public ClientInitialAccessMapper emitKey() {
- emit = EmitValue.KEY;
- return this;
- }
-
- public ClientInitialAccessMapper expired(int time) {
- this.expired = time;
- return this;
- }
-
- @Override
- public void map(String key, SessionEntity e, Collector collector) {
- if (!realm.equals(e.getRealm())) {
- return;
- }
-
- if (!(e instanceof ClientInitialAccessEntity)) {
- return;
- }
-
- ClientInitialAccessEntity entity = (ClientInitialAccessEntity) e;
-
- boolean include = false;
-
- if (expired != null) {
- if (entity.getRemainingCount() <= 0) {
- include = true;
- } else if (entity.getExpiration() > 0 && (entity.getTimestamp() + entity.getExpiration()) < expired) {
- include = true;
- }
- } else {
- include = true;
- }
-
- if (include) {
- switch (emit) {
- case KEY:
- collector.emit(key, key);
- break;
- case ENTITY:
- collector.emit(key, entity);
- break;
- }
- }
- }
-
-}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/stream/ClientInitialAccessPredicate.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/stream/ClientInitialAccessPredicate.java
deleted file mode 100644
index 8763642fac..0000000000
--- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/stream/ClientInitialAccessPredicate.java
+++ /dev/null
@@ -1,76 +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.models.sessions.infinispan.stream;
-
-import org.keycloak.models.sessions.infinispan.entities.ClientInitialAccessEntity;
-import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
-
-import java.io.Serializable;
-import java.util.Map;
-import java.util.function.Predicate;
-
-/**
- * @author Stian Thorgersen
- */
-public class ClientInitialAccessPredicate implements Predicate>, Serializable {
-
- public ClientInitialAccessPredicate(String realm) {
- this.realm = realm;
- }
-
- private String realm;
-
- private Integer expired;
-
- public static ClientInitialAccessPredicate create(String realm) {
- return new ClientInitialAccessPredicate(realm);
- }
-
- public ClientInitialAccessPredicate expired(int time) {
- this.expired = time;
- return this;
- }
-
- @Override
- public boolean test(Map.Entry entry) {
- SessionEntity e = entry.getValue();
-
- if (!realm.equals(e.getRealm())) {
- return false;
- }
-
- if (!(e instanceof ClientInitialAccessEntity)) {
- return false;
- }
-
- ClientInitialAccessEntity entity = (ClientInitialAccessEntity) e;
-
- if (expired != null) {
- if (entity.getRemainingCount() <= 0) {
- return true;
- } else if (entity.getExpiration() > 0 && (entity.getTimestamp() + entity.getExpiration()) < expired) {
- return true;
- } else {
- return false;
- }
- }
-
- return true;
- }
-
-}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaRealmProvider.java b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaRealmProvider.java
index 306f674b7a..c7dea5a5a8 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaRealmProvider.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaRealmProvider.java
@@ -18,10 +18,24 @@
package org.keycloak.models.jpa;
import org.jboss.logging.Logger;
+import org.keycloak.common.util.Time;
import org.keycloak.connections.jpa.util.JpaUtils;
import org.keycloak.migration.MigrationModel;
-import org.keycloak.models.*;
-import org.keycloak.models.jpa.entities.*;
+import org.keycloak.models.ClientInitialAccessModel;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.ClientTemplateModel;
+import org.keycloak.models.GroupModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.RealmProvider;
+import org.keycloak.models.RoleContainerModel;
+import org.keycloak.models.RoleModel;
+import org.keycloak.models.jpa.entities.ClientEntity;
+import org.keycloak.models.jpa.entities.ClientInitialAccessEntity;
+import org.keycloak.models.jpa.entities.ClientTemplateEntity;
+import org.keycloak.models.jpa.entities.GroupEntity;
+import org.keycloak.models.jpa.entities.RealmEntity;
+import org.keycloak.models.jpa.entities.RoleEntity;
import org.keycloak.models.utils.KeycloakModelUtils;
import javax.persistence.EntityManager;
@@ -136,6 +150,8 @@ public class JpaRealmProvider implements RealmProvider {
removeRole(adapter, role);
}
+ num = em.createNamedQuery("removeClientInitialAccessByRealm")
+ .setParameter("realm", realm).executeUpdate();
em.remove(realm);
@@ -557,4 +573,82 @@ public class JpaRealmProvider implements RealmProvider {
}
return Collections.unmodifiableList(list);
}
+
+ @Override
+ public ClientInitialAccessModel createClientInitialAccessModel(RealmModel realm, int expiration, int count) {
+ RealmEntity realmEntity = em.find(RealmEntity.class, realm.getId());
+
+ ClientInitialAccessEntity entity = new ClientInitialAccessEntity();
+ entity.setId(KeycloakModelUtils.generateId());
+ entity.setRealm(realmEntity);
+
+ entity.setCount(count);
+ entity.setRemainingCount(count);
+
+ int currentTime = Time.currentTime();
+ entity.setTimestamp(currentTime);
+ entity.setExpiration(expiration);
+
+ em.persist(entity);
+
+ return entityToModel(entity);
+ }
+
+ @Override
+ public ClientInitialAccessModel getClientInitialAccessModel(RealmModel realm, String id) {
+ ClientInitialAccessEntity entity = em.find(ClientInitialAccessEntity.class, id);
+ if (entity == null) {
+ return null;
+ } else {
+ return entityToModel(entity);
+ }
+ }
+
+ @Override
+ public void removeClientInitialAccessModel(RealmModel realm, String id) {
+ ClientInitialAccessEntity entity = em.find(ClientInitialAccessEntity.class, id);
+ if (entity != null) {
+ em.remove(entity);
+ em.flush();
+ }
+ }
+
+ @Override
+ public List listClientInitialAccess(RealmModel realm) {
+ RealmEntity realmEntity = em.find(RealmEntity.class, realm.getId());
+
+ TypedQuery query = em.createNamedQuery("findClientInitialAccessByRealm", ClientInitialAccessEntity.class);
+ query.setParameter("realm", realmEntity);
+ List entities = query.getResultList();
+
+ return entities.stream()
+ .map(entity -> entityToModel(entity))
+ .collect(Collectors.toList());
+ }
+
+ @Override
+ public void removeExpiredClientInitialAccess() {
+ int currentTime = Time.currentTime();
+
+ em.createNamedQuery("removeExpiredClientInitialAccess")
+ .setParameter("currentTime", currentTime)
+ .executeUpdate();
+ }
+
+ @Override
+ public void decreaseRemainingCount(RealmModel realm, ClientInitialAccessModel clientInitialAccess) {
+ em.createNamedQuery("decreaseClientInitialAccessRemainingCount")
+ .setParameter("id", clientInitialAccess.getId())
+ .executeUpdate();
+ }
+
+ private ClientInitialAccessModel entityToModel(ClientInitialAccessEntity entity) {
+ ClientInitialAccessModel model = new ClientInitialAccessModel();
+ model.setId(entity.getId());
+ model.setCount(entity.getCount());
+ model.setRemainingCount(entity.getRemainingCount());
+ model.setExpiration(entity.getExpiration());
+ model.setTimestamp(entity.getTimestamp());
+ return model;
+ }
}
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 a217af3a8c..b71a493d01 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
@@ -1865,6 +1865,7 @@ public class RealmAdapter implements RealmModel, JpaModel {
ComponentEntity c = em.find(ComponentEntity.class, component.getId());
if (c == null) return;
session.users().preRemove(this, component);
+ ComponentUtil.notifyPreRemove(session, this, component);
removeComponents(component.getId());
getEntity().getComponents().remove(c);
}
@@ -1876,7 +1877,10 @@ public class RealmAdapter implements RealmModel, JpaModel {
getEntity().getComponents().stream()
.filter(sameParent)
.map(this::entityToModel)
- .forEach(c -> session.users().preRemove(this, c));
+ .forEach((ComponentModel c) -> {
+ session.users().preRemove(this, c);
+ ComponentUtil.notifyPreRemove(session, this, c);
+ });
getEntity().getComponents().removeIf(sameParent);
}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientInitialAccessEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientInitialAccessEntity.java
new file mode 100644
index 0000000000..bdb510e7c4
--- /dev/null
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientInitialAccessEntity.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2017 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 Marek Posolda
+ */
+@Entity
+@Table(name="CLIENT_INITIAL_ACCESS")
+@NamedQueries({
+ @NamedQuery(name="findClientInitialAccessByRealm", query="select ia from ClientInitialAccessEntity ia where ia.realm = :realm order by timestamp"),
+ @NamedQuery(name="removeClientInitialAccessByRealm", query="delete from ClientInitialAccessEntity ia where ia.realm = :realm"),
+ @NamedQuery(name="removeExpiredClientInitialAccess", query="delete from ClientInitialAccessEntity ia where (ia.expiration > 0 and (ia.timestamp + ia.expiration) < :currentTime) or ia.remainingCount = 0"),
+ @NamedQuery(name="decreaseClientInitialAccessRemainingCount", query="update ClientInitialAccessEntity ia set ia.remainingCount = ia.remainingCount - 1 where ia.id = :id")
+})
+public class ClientInitialAccessEntity {
+
+ @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;
+
+ @Column(name="TIMESTAMP")
+ private int timestamp;
+
+ @Column(name="EXPIRATION")
+ private int expiration;
+
+ @Column(name="COUNT")
+ private int count;
+
+ @Column(name="REMAINING_COUNT")
+ private int remainingCount;
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "REALM_ID")
+ protected RealmEntity realm;
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public int getTimestamp() {
+ return timestamp;
+ }
+
+ public void setTimestamp(int timestamp) {
+ this.timestamp = timestamp;
+ }
+
+ public int getExpiration() {
+ return expiration;
+ }
+
+ public void setExpiration(int expiration) {
+ this.expiration = expiration;
+ }
+
+ public int getCount() {
+ return count;
+ }
+
+ public void setCount(int count) {
+ this.count = count;
+ }
+
+ public int getRemainingCount() {
+ return remainingCount;
+ }
+
+ public void setRemainingCount(int remainingCount) {
+ this.remainingCount = remainingCount;
+ }
+
+ public RealmEntity getRealm() {
+ return realm;
+ }
+
+ public void setRealm(RealmEntity realm) {
+ this.realm = realm;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null) return false;
+ if (!(o instanceof ClientInitialAccessEntity)) return false;
+
+ ClientInitialAccessEntity that = (ClientInitialAccessEntity) o;
+
+ if (!id.equals(that.id)) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return id.hashCode();
+ }
+}
diff --git a/model/jpa/src/main/resources/META-INF/jpa-changelog-3.2.0.xml b/model/jpa/src/main/resources/META-INF/jpa-changelog-3.2.0.xml
index b3872b82c5..bd55645295 100644
--- a/model/jpa/src/main/resources/META-INF/jpa-changelog-3.2.0.xml
+++ b/model/jpa/src/main/resources/META-INF/jpa-changelog-3.2.0.xml
@@ -22,6 +22,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -158,5 +174,9 @@
+
+
+
+
diff --git a/model/jpa/src/main/resources/META-INF/persistence.xml b/model/jpa/src/main/resources/META-INF/persistence.xml
index 5d3fa81832..f23198c387 100755
--- a/model/jpa/src/main/resources/META-INF/persistence.xml
+++ b/model/jpa/src/main/resources/META-INF/persistence.xml
@@ -56,6 +56,7 @@
org.keycloak.models.jpa.entities.UserGroupMembershipEntityorg.keycloak.models.jpa.entities.ClientTemplateEntityorg.keycloak.models.jpa.entities.TemplateScopeMappingEntity
+ org.keycloak.models.jpa.entities.ClientInitialAccessEntityorg.keycloak.events.jpa.EventEntity
diff --git a/saml-core/src/main/java/org/keycloak/saml/common/util/DocumentUtil.java b/saml-core/src/main/java/org/keycloak/saml/common/util/DocumentUtil.java
index b5f2232a47..f516124f7b 100755
--- a/saml-core/src/main/java/org/keycloak/saml/common/util/DocumentUtil.java
+++ b/saml-core/src/main/java/org/keycloak/saml/common/util/DocumentUtil.java
@@ -52,6 +52,7 @@ import java.io.InputStream;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
+import java.util.Objects;
/**
* Utility dealing with DOM
@@ -554,4 +555,33 @@ public class DocumentUtil {
return documentBuilderFactory;
}
+
+ /**
+ * Get a (direct) child {@linkplain Element} from the parent {@linkplain Element}.
+ *
+ * @param parent parent element
+ * @param targetNamespace namespace URI
+ * @param targetLocalName local name
+ * @return a child element matching the target namespace and localname, where {@linkplain Element#getParentNode()} is the parent input parameter
+ * @return
+ */
+
+ public static Element getDirectChildElement(Element parent, String targetNamespace, String targetLocalName) {
+ Node child = parent.getFirstChild();
+
+ while(child != null) {
+ if(child instanceof Element) {
+ Element childElement = (Element)child;
+
+ String ns = childElement.getNamespaceURI();
+ String localName = childElement.getLocalName();
+
+ if(Objects.equals(targetNamespace, ns) && Objects.equals(targetLocalName, localName)) {
+ return childElement;
+ }
+ }
+ child = child.getNextSibling();
+ }
+ return null;
+ }
}
\ No newline at end of file
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/api/saml/v2/sig/SAML2Signature.java b/saml-core/src/main/java/org/keycloak/saml/processing/api/saml/v2/sig/SAML2Signature.java
index ef3e3bd736..09ce9d62f8 100755
--- a/saml-core/src/main/java/org/keycloak/saml/processing/api/saml/v2/sig/SAML2Signature.java
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/api/saml/v2/sig/SAML2Signature.java
@@ -49,8 +49,6 @@ public class SAML2Signature {
private static final PicketLinkLogger logger = PicketLinkLoggerFactory.getLogger();
- private static final String ID_ATTRIBUTE_NAME = "ID";
-
private String signatureMethod = SignatureMethod.RSA_SHA1;
private String digestMethod = DigestMethod.SHA1;
@@ -156,7 +154,7 @@ public class SAML2Signature {
*/
public void signSAMLDocument(Document samlDocument, String keyName, KeyPair keypair, String canonicalizationMethodType) throws ProcessingException {
// Get the ID from the root
- String id = samlDocument.getDocumentElement().getAttribute(ID_ATTRIBUTE_NAME);
+ String id = samlDocument.getDocumentElement().getAttribute(JBossSAMLConstants.ID.get());
try {
sign(samlDocument, id, keyName, keypair, canonicalizationMethodType);
} catch (ParserConfigurationException | GeneralSecurityException | MarshalException | XMLSignatureException e) {
@@ -210,18 +208,20 @@ public class SAML2Signature {
*
* @param document SAML document to have its ID attribute configured.
*/
- private void configureIdAttribute(Document document) {
+ public static void configureIdAttribute(Document document) {
// Estabilish the IDness of the ID attribute.
- document.getDocumentElement().setIdAttribute(ID_ATTRIBUTE_NAME, true);
+ configureIdAttribute(document.getDocumentElement());
NodeList nodes = document.getElementsByTagNameNS(JBossSAMLURIConstants.ASSERTION_NSURI.get(),
JBossSAMLConstants.ASSERTION.get());
for (int i = 0; i < nodes.getLength(); i++) {
- Node n = nodes.item(i);
- if (n instanceof Element) {
- ((Element) n).setIdAttribute(ID_ATTRIBUTE_NAME, true);
- }
+ configureIdAttribute((Element) nodes.item(i));
}
}
+
+ public static void configureIdAttribute(Element element) {
+ element.setIdAttribute(JBossSAMLConstants.ID.get(), true);
+ }
+
}
\ No newline at end of file
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/util/AssertionUtil.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/util/AssertionUtil.java
index bb15d233bf..244fb7d182 100755
--- a/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/util/AssertionUtil.java
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/util/AssertionUtil.java
@@ -49,11 +49,12 @@ import org.keycloak.saml.processing.core.parsers.saml.SAMLParser;
import org.keycloak.saml.processing.core.saml.v2.writers.SAMLAssertionWriter;
import org.keycloak.saml.processing.core.util.JAXPValidationUtil;
import org.keycloak.saml.processing.core.util.XMLEncryptionUtil;
-
+import org.keycloak.saml.processing.core.util.XMLSignatureUtil;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
+import javax.xml.crypto.dsig.XMLSignature;
import javax.xml.datatype.XMLGregorianCalendar;
import javax.xml.namespace.QName;
import java.io.ByteArrayInputStream;
@@ -267,42 +268,56 @@ public class AssertionUtil {
}
/**
- * Given an assertion element, validate the signature
+ * Given an {@linkplain Element}, validate the Signature direct child element
*
- * @param assertionElement
+ * @param element parent {@linkplain Element}
* @param publicKey the {@link PublicKey}
*
- * @return
+ * @return true if signature is present and valid
*/
- public static boolean isSignatureValid(Element assertionElement, PublicKey publicKey) {
- try {
- Document doc = DocumentUtil.createDocument();
- Node n = doc.importNode(assertionElement, true);
- doc.appendChild(n);
-
- return new SAML2Signature().validate(doc, new HardcodedKeyLocator(publicKey));
- } catch (Exception e) {
- logger.signatureAssertionValidationError(e);
- }
- return false;
+ public static boolean isSignatureValid(Element element, PublicKey publicKey) {
+ return isSignatureValid(element, new HardcodedKeyLocator(publicKey));
}
/**
- * Given an assertion element, validate the signature.
+ * Given an {@linkplain Element}, validate the Signature direct child element
+ *
+ * @param element parent {@linkplain Element}
+ * @param keyLocator the {@link KeyLocator}
+ *
+ * @return true if signature is present and valid
*/
- public static boolean isSignatureValid(Element assertionElement, KeyLocator keyLocator) {
+
+ public static boolean isSignatureValid(Element element, KeyLocator keyLocator) {
try {
- Document doc = DocumentUtil.createDocument();
- Node n = doc.importNode(assertionElement, true);
- doc.appendChild(n);
-
- return new SAML2Signature().validate(doc, keyLocator);
+ SAML2Signature.configureIdAttribute(element);
+
+ Element signature = getSignature(element);
+ if(signature != null) {
+ return XMLSignatureUtil.validateSingleNode(signature, keyLocator);
+ }
} catch (Exception e) {
logger.signatureAssertionValidationError(e);
}
return false;
}
+
+ /**
+ *
+ * Given an {@linkplain Element}, check if there is a Signature direct child element
+ *
+ * @param element parent {@linkplain Element}
+ * @return true if signature is present
+ */
+ public static boolean isSignedElement(Element element) {
+ return getSignature(element) != null;
+ }
+
+ protected static Element getSignature(Element element) {
+ return DocumentUtil.getDirectChildElement(element, XMLSignature.XMLNS, "Signature");
+ }
+
/**
* Check whether the assertion has expired
*
@@ -570,8 +585,8 @@ public class AssertionUtil {
/**
* This method modifies the given responseType, and replaces the encrypted assertion with a decrypted version.
- *
- * It returns the assertion element as it was decrypted. This can be used in sginature verification.
+ * @param responseType a response containg an encrypted assertion
+ * @return the assertion element as it was decrypted. This can be used in signature verification.
*/
public static Element decryptAssertion(ResponseType responseType, PrivateKey privateKey) throws ParsingException, ProcessingException, ConfigurationException {
SAML2Response saml2Response = new SAML2Response();
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/util/XMLSignatureUtil.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/util/XMLSignatureUtil.java
index 53228c971a..7093a20391 100755
--- a/saml-core/src/main/java/org/keycloak/saml/processing/core/util/XMLSignatureUtil.java
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/util/XMLSignatureUtil.java
@@ -468,7 +468,7 @@ public class XMLSignatureUtil {
return true;
}
- private static boolean validateSingleNode(Node signatureNode, final KeyLocator locator) throws MarshalException, XMLSignatureException {
+ public static boolean validateSingleNode(Node signatureNode, final KeyLocator locator) throws MarshalException, XMLSignatureException {
KeySelectorUtilizingKeyNameHint sel = new KeySelectorUtilizingKeyNameHint(locator);
try {
if (validateUsingKeySelector(signatureNode, sel)) {
diff --git a/saml-core/src/test/java/org/keycloak/saml/processing/core/saml/v2/util/AssertionUtilTest.java b/saml-core/src/test/java/org/keycloak/saml/processing/core/saml/v2/util/AssertionUtilTest.java
new file mode 100644
index 0000000000..b60bb4b893
--- /dev/null
+++ b/saml-core/src/test/java/org/keycloak/saml/processing/core/saml/v2/util/AssertionUtilTest.java
@@ -0,0 +1,58 @@
+package org.keycloak.saml.processing.core.saml.v2.util;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.security.cert.X509Certificate;
+
+import org.bouncycastle.util.Arrays;
+import org.junit.Test;
+import org.keycloak.common.util.Base64;
+import org.keycloak.common.util.DerUtils;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+public class AssertionUtilTest {
+
+ private static final String PRIVATE_KEY = "MIICWwIBAAKBgQDVG8a7xGN6ZIkDbeecySygcDfsypjUMNPE4QJjis8B316CvsZQ0hcTTLUyiRpHlHZys2k3xEhHBHymFC1AONcvzZzpb40tAhLHO1qtAnut00khjAdjR3muLVdGkM/zMC7G5s9iIwBVhwOQhy+VsGnCH91EzkjZ4SVEr55KJoyQJQIDAQABAoGADaTtoG/+foOZUiLjRWKL/OmyavK9vjgyFtThNkZY4qHOh0h3og0RdSbgIxAsIpEa1FUwU2W5yvI6mNeJ3ibFgCgcxqPk6GkAC7DWfQfdQ8cS+dCuaFTs8ObIQEvU50YzeNPiiFxRA+MnauCUXaKm/PnDfjd4tPgru7XZvlGh0wECQQDsBbN2cKkBKpr/b5oJiBcBaSZtWiMNuYBDn9x8uORj+Gy/49BUIMHF2EWyxOWz6ocP5YiynNRkPe21Zus7PEr1AkEA5yWQOkxUTIg43s4pxNSeHtL+Ebqcg54lY2xOQK0yufxUVZI8ODctAKmVBMiCKpU3mZQquOaQicuGtocpgxlScQI/YM31zZ5nsxLGf/5GL6KhzPJT0IYn2nk7IoFu7bjn9BjwgcPurpLA52TNMYWQsTqAKwT6DEhG1NaRqNWNpb4VAkBehObAYBwMm5udyHIeEc+CzUalm0iLLa0eRdiN7AUVNpCJ2V2Uo0NcxPux1AgeP5xXydXafDXYkwhINWcNO9qRAkEA58ckAC5loUGwU5dLaugsGH/a2Q8Ac8bmPglwfCstYDpl8Gp/eimb1eKyvDEELOhyImAv4/uZV9wN85V0xZXWsw==";
+
+ /**
+ * The public certificate that corresponds to {@link #PRIVATE_KEY}.
+ */
+ private static final String PUBLIC_CERT = "MIIDdzCCAl+gAwIBAgIEbySuqTANBgkqhkiG9w0BAQsFADBsMRAwDgYDVQQGEwdVbmtub3duMRAwDgYDVQQIEwdVbmtub3duMRAwDgYDVQQHEwdVbmtub3duMRAwDgYDVQQKEwdVbmtub3duMRAwDgYDVQQLEwdVbmtub3duMRAwDgYDVQQDEwdVbmtub3duMB4XDTE1MDEyODIyMTYyMFoXDTE3MTAyNDIyMTYyMFowbDEQMA4GA1UEBhMHVW5rbm93bjEQMA4GA1UECBMHVW5rbm93bjEQMA4GA1UEBxMHVW5rbm93bjEQMA4GA1UEChMHVW5rbm93bjEQMA4GA1UECxMHVW5rbm93bjEQMA4GA1UEAxMHVW5rbm93bjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAII/K9NNvXi9IySl7+l2zY/kKrGTtuR4WdCI0xLW/Jn4dLY7v1/HOnV4CC4ecFOzhdNFPtJkmEhP/q62CpmOYOKApXk3tfmm2rwEz9bWprVxgFGKnbrWlz61Z/cjLAlhD3IUj2ZRBquYgSXQPsYfXo1JmSWF5pZ9uh1FVqu9f4wvRqY20ZhUN+39F+1iaBsoqsrbXypCn1HgZkW1/9D9GZug1c3vB4wg1TwZZWRNGtxwoEhdK6dPrNcZ+6PdanVilWrbQFbBjY4wz8/7IMBzssoQ7Usmo8F1Piv0FGfaVeJqBrcAvbiBMpk8pT+27u6p8VyIX6LhGvnxIwM07NByeSUCAwEAAaMhMB8wHQYDVR0OBBYEFFlcNuTYwI9W0tQ224K1gFJlMam0MA0GCSqGSIb3DQEBCwUAA4IBAQB5snl1KWOJALtAjLqD0mLPg1iElmZP82Lq1htLBt3XagwzU9CaeVeCQ7lTp+DXWzPa9nCLhsC3QyrV3/+oqNli8C6NpeqI8FqN2yQW/QMWN1m5jWDbmrWwtQzRUn/rh5KEb5m3zPB+tOC6e/2bV3QeQebxeW7lVMD0tSCviUg1MQf1l2gzuXQo60411YwqrXwk6GMkDOhFDQKDlMchO3oRbQkGbcP8UeiKAXjMeHfzbiBr+cWz8NYZEtxUEDYDjTpKrYCSMJBXpmgVJCZ00BswbksxJwaGqGMPpUKmCV671pf3m8nq3xyiHMDGuGwtbU+GE8kVx85menmp8+964nin";
+
+ @Test
+ public void testSaml20Signed() throws Exception {
+
+ X509Certificate decodeCertificate = DerUtils.decodeCertificate(new ByteArrayInputStream(Base64.decode(PUBLIC_CERT)));
+
+ try (InputStream st = AssertionUtilTest.class.getResourceAsStream("saml20-signed-response.xml")) {
+ Document document = DocumentUtil.getDocument(st);
+
+ Element assertion = DocumentUtil.getDirectChildElement(document.getDocumentElement(), "urn:oasis:names:tc:SAML:2.0:assertion", "Assertion");
+
+ assertTrue(AssertionUtil.isSignatureValid(assertion, decodeCertificate.getPublicKey()));
+
+ // test manipulation of signature
+ Element signatureElement = AssertionUtil.getSignature(assertion);
+ byte[] validSignature = Base64.decode(signatureElement.getTextContent());
+
+ // change the signature value slightly
+ byte[] invalidSignature = Arrays.clone(validSignature);
+ invalidSignature[0] ^= invalidSignature[0];
+ signatureElement.setTextContent(Base64.encodeBytes(invalidSignature));
+
+ // check that signature now is invalid
+ assertFalse(AssertionUtil.isSignatureValid(document.getDocumentElement(), decodeCertificate.getPublicKey()));
+
+ // restore valid signature, but remove Signature element, check that still invalid
+ signatureElement.setTextContent(Base64.encodeBytes(validSignature));
+
+ assertion.removeChild(signatureElement);
+ assertFalse(AssertionUtil.isSignatureValid(document.getDocumentElement(), decodeCertificate.getPublicKey()));
+ }
+ }
+
+}
diff --git a/saml-core/src/test/resources/org/keycloak/saml/processing/core/saml/v2/util/saml20-signed-response.xml b/saml-core/src/test/resources/org/keycloak/saml/processing/core/saml/v2/util/saml20-signed-response.xml
new file mode 100644
index 0000000000..998520addb
--- /dev/null
+++ b/saml-core/src/test/resources/org/keycloak/saml/processing/core/saml/v2/util/saml20-signed-response.xml
@@ -0,0 +1 @@
+http://localhost:8080/auth/realms/saml-broker-realmhttp://localhost:8080/auth/realms/saml-broker-realmcCSNXxLmu411weW1kRpie4C9yaBg2In6V4oEuqya0Eo=Qe6ZqgSwFH31UTu+zHqr1/UsafH0luxP5OH/cqyHm07Kf/Fp/fm9mnHJ0kGoUn0SUo7xWvwy8AzUfPXWMYS3kDyhUsPzgz0CnCzzfTz3koKFczgyIQ8sokIDv0cTp3z1qCUVWV0CEPzhtWlaIus2W89TEi/h9KjYrkeGl3+cpm8BPEAt4EP8Oht5czK2haIfPMDUm5Y7uw/FCSsvSfFyrlJ0jR/YMeP9PP0InYYegI9QQgvXKRm6DZSNZgKYFpprc12v6vv/zTaMm5fbuuy1wNDuDTB8EF6K1yrq21DatJXUKE1oOMBrkOvbFJNtgHlQviz1OssAqzHlf0NQPIAEig==IzH2UxfMxovYTEHn4Bh-EAj-Zrvldukl_5Snu0RA0B8MIICsTCCAZkCBgFb1GERYzANBgkqhkiG9w0BAQsFADAcMRowGAYDVQQDDBFzYW1sLWJyb2tlci1yZWFsbTAeFw0xNzA1MDQxNjUxMjJaFw0yNzA1MDQxNjUzMDJaMBwxGjAYBgNVBAMMEXNhbWwtYnJva2VyLXJlYWxtMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAgj8r0029eL0jJKXv6XbNj+QqsZO25HhZ0IjTEtb8mfh0tju/X8c6dXgILh5wU7OF00U+0mSYSE/+rrYKmY5g4oCleTe1+abavATP1tamtXGAUYqdutaXPrVn9yMsCWEPchSPZlEGq5iBJdA+xh9ejUmZJYXmln26HUVWq71/jC9GpjbRmFQ37f0X7WJoGyiqyttfKkKfUeBmRbX/0P0Zm6DVze8HjCDVPBllZE0a3HCgSF0rp0+s1xn7o91qdWKVattAVsGNjjDPz/sgwHOyyhDtSyajwXU+K/QUZ9pV4moGtwC9uIEymTylP7bu7qnxXIhfouEa+fEjAzTs0HJ5JQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQCBuWKmcVPX4sZtpvBp8BgQwH51l5tKPuQq66/4JH40hzkrLOGcVEOafIsGoOWi8HsZWZu+APwhSrnRNd7yMV+NJyw5W1DKjhdUPWnzPNy+UMAcoUBFhXDIWog0qxgGvTdoe1lfHryUQt1cd95SFVIerJA93nFbSOoMB+N7TmfQm+sNu2pJ2tr6mx3wGCXMnWf29gwhCI3wV19hh4KugnMIEStjvQoRyh2yna64BrR3eaUyhU/Bdrq2VXLNU/9WXg9gbRLUEWkMUPKOeQ5cGCgc4JFyFXRo5ExkzmvP9vwBRtjQulk5QKqfYo251mKvTQgO7K8d4CzVS/4+bpgKvZAMgj8r0029eL0jJKXv6XbNj+QqsZO25HhZ0IjTEtb8mfh0tju/X8c6dXgILh5wU7OF00U+0mSYSE/+rrYKmY5g4oCleTe1+abavATP1tamtXGAUYqdutaXPrVn9yMsCWEPchSPZlEGq5iBJdA+xh9ejUmZJYXmln26HUVWq71/jC9GpjbRmFQ37f0X7WJoGyiqyttfKkKfUeBmRbX/0P0Zm6DVze8HjCDVPBllZE0a3HCgSF0rp0+s1xn7o91qdWKVattAVsGNjjDPz/sgwHOyyhDtSyajwXU+K/QUZ9pV4moGtwC9uIEymTylP7bu7qnxXIhfouEa+fEjAzTs0HJ5JQ==AQABG-ebb3a66f-686f-4bb9-8a8b-20b566ca747bhttp://localhost:8080/auth/realms/saml-broker-authentication-realmurn:oasis:names:tc:SAML:2.0:ac:classes:unspecifiedmanageruser
\ No newline at end of file
diff --git a/server-spi-private/src/main/java/org/keycloak/authorization/AuthorizationProvider.java b/server-spi-private/src/main/java/org/keycloak/authorization/AuthorizationProvider.java
index 8a55ff14c3..644b90ac37 100644
--- a/server-spi-private/src/main/java/org/keycloak/authorization/AuthorizationProvider.java
+++ b/server-spi-private/src/main/java/org/keycloak/authorization/AuthorizationProvider.java
@@ -21,14 +21,17 @@ package org.keycloak.authorization;
import java.util.Collection;
import java.util.List;
import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
import org.keycloak.authorization.model.Policy;
+import org.keycloak.authorization.model.Resource;
import org.keycloak.authorization.model.ResourceServer;
+import org.keycloak.authorization.model.Scope;
import org.keycloak.authorization.permission.evaluator.Evaluators;
import org.keycloak.authorization.policy.evaluation.DefaultPolicyEvaluator;
import org.keycloak.authorization.policy.provider.PolicyProvider;
import org.keycloak.authorization.policy.provider.PolicyProviderFactory;
-import org.keycloak.authorization.store.AuthorizationStoreFactory;
import org.keycloak.authorization.store.PolicyStore;
import org.keycloak.authorization.store.ResourceServerStore;
import org.keycloak.authorization.store.ResourceStore;
@@ -37,7 +40,6 @@ import org.keycloak.authorization.store.StoreFactory;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.cache.authorization.CachedStoreFactoryProvider;
-import org.keycloak.models.cache.authorization.CachedStoreProviderFactory;
import org.keycloak.models.utils.RepresentationToModel;
import org.keycloak.provider.Provider;
import org.keycloak.representations.idm.authorization.AbstractPolicyRepresentation;
@@ -143,6 +145,61 @@ public final class AuthorizationProvider implements Provider {
return new PolicyStore() {
@Override
public Policy create(AbstractPolicyRepresentation representation, ResourceServer resourceServer) {
+ Set resources = representation.getResources();
+
+ if (resources != null) {
+ representation.setResources(resources.stream().map(id -> {
+ Resource resource = getResourceStore().findById(id, resourceServer.getId());
+
+ if (resource == null) {
+ resource = getResourceStore().findByName(id, resourceServer.getId());
+ }
+
+ if (resource == null) {
+ throw new RuntimeException("Resource [" + id + "] does not exist");
+ }
+
+ return resource.getId();
+ }).collect(Collectors.toSet()));
+ }
+
+ Set scopes = representation.getScopes();
+
+ if (scopes != null) {
+ representation.setScopes(scopes.stream().map(id -> {
+ Scope scope = getScopeStore().findById(id, resourceServer.getId());
+
+ if (scope == null) {
+ scope = getScopeStore().findByName(id, resourceServer.getId());
+ }
+
+ if (scope == null) {
+ throw new RuntimeException("Scope [" + id + "] does not exist");
+ }
+
+ return scope.getId();
+ }).collect(Collectors.toSet()));
+ }
+
+
+ Set policies = representation.getPolicies();
+
+ if (policies != null) {
+ representation.setPolicies(policies.stream().map(id -> {
+ Policy policy = getPolicyStore().findById(id, resourceServer.getId());
+
+ if (policy == null) {
+ policy = getPolicyStore().findByName(id, resourceServer.getId());
+ }
+
+ if (policy == null) {
+ throw new RuntimeException("Policy [" + id + "] does not exist");
+ }
+
+ return policy.getId();
+ }).collect(Collectors.toSet()));
+ }
+
return RepresentationToModel.toModel(representation, AuthorizationProvider.this, policyStore.create(representation, resourceServer));
}
diff --git a/server-spi-private/src/main/java/org/keycloak/authorization/attribute/Attributes.java b/server-spi-private/src/main/java/org/keycloak/authorization/attribute/Attributes.java
index c83d9f8146..0719bab259 100644
--- a/server-spi-private/src/main/java/org/keycloak/authorization/attribute/Attributes.java
+++ b/server-spi-private/src/main/java/org/keycloak/authorization/attribute/Attributes.java
@@ -107,6 +107,10 @@ public interface Attributes {
return values.length;
}
+ public boolean isEmpty() {
+ return values.length == 0;
+ }
+
public String asString(int idx) {
if (idx >= values.length) {
throw new IllegalArgumentException("Invalid index [" + idx + "]. Values are [" + values + "].");
diff --git a/server-spi-private/src/main/java/org/keycloak/events/admin/ResourceType.java b/server-spi-private/src/main/java/org/keycloak/events/admin/ResourceType.java
index cba5ed6672..128b34451e 100644
--- a/server-spi-private/src/main/java/org/keycloak/events/admin/ResourceType.java
+++ b/server-spi-private/src/main/java/org/keycloak/events/admin/ResourceType.java
@@ -156,5 +156,25 @@ public enum ResourceType {
/**
*
*/
- , COMPONENT;
+ , COMPONENT
+
+ /**
+ *
+ */
+ , AUTHORIZATION_RESOURCE_SERVER
+
+ /**
+ *
+ */
+ , AUTHORIZATION_RESOURCE
+
+ /**
+ *
+ */
+ , AUTHORIZATION_SCOPE
+
+ /**
+ *
+ */
+ , AUTHORIZATION_POLICY;
}
diff --git a/server-spi-private/src/main/java/org/keycloak/forms/login/LoginFormsProvider.java b/server-spi-private/src/main/java/org/keycloak/forms/login/LoginFormsProvider.java
index ac435e3b9c..cb203c17ce 100755
--- a/server-spi-private/src/main/java/org/keycloak/forms/login/LoginFormsProvider.java
+++ b/server-spi-private/src/main/java/org/keycloak/forms/login/LoginFormsProvider.java
@@ -120,4 +120,6 @@ public interface LoginFormsProvider extends Provider {
public LoginFormsProvider setStatus(Response.Status status);
LoginFormsProvider setActionUri(URI requestUri);
+
+ LoginFormsProvider setExecution(String execution);
}
diff --git a/server-spi-private/src/main/java/org/keycloak/models/utils/ComponentUtil.java b/server-spi-private/src/main/java/org/keycloak/models/utils/ComponentUtil.java
index 1d41f0dd4b..0c603c44c6 100644
--- a/server-spi-private/src/main/java/org/keycloak/models/utils/ComponentUtil.java
+++ b/server-spi-private/src/main/java/org/keycloak/models/utils/ComponentUtil.java
@@ -17,6 +17,7 @@
package org.keycloak.models.utils;
+import org.jboss.logging.Logger;
import org.keycloak.component.ComponentFactory;
import org.keycloak.component.ComponentModel;
import org.keycloak.models.KeycloakSession;
@@ -38,6 +39,8 @@ import java.util.Map;
*/
public class ComponentUtil {
+ private static final Logger logger = Logger.getLogger(ComponentUtil.class);
+
public static Map getComponentConfigProperties(KeycloakSession session, ComponentRepresentation component) {
return getComponentConfigProperties(session, component.getProviderType(), component.getProviderId());
}
@@ -102,5 +105,14 @@ public class ComponentUtil {
((OnUpdateComponent)session.userStorageManager()).onUpdate(session, realm, oldModel, newModel);
}
}
+ public static void notifyPreRemove(KeycloakSession session, RealmModel realm, ComponentModel model) {
+ try {
+ ComponentFactory factory = getComponentFactory(session, model);
+ factory.preRemove(session, realm, model);
+ } catch (IllegalArgumentException iae) {
+ // We allow to remove broken providers without throwing an exception
+ logger.warn(iae.getMessage());
+ }
+ }
}
diff --git a/server-spi-private/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java b/server-spi-private/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
index ab32a22df9..f5b48faef4 100755
--- a/server-spi-private/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
+++ b/server-spi-private/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
@@ -738,11 +738,7 @@ public class ModelToRepresentation {
return rep;
}
- public static ScopeRepresentation toRepresentation(Scope model, AuthorizationProvider authorizationProvider) {
- return toRepresentation(model, authorizationProvider, true);
- }
-
- public static ScopeRepresentation toRepresentation(Scope model, AuthorizationProvider authorizationProvider, boolean deep) {
+ public static ScopeRepresentation toRepresentation(Scope model) {
ScopeRepresentation scope = new ScopeRepresentation();
scope.setId(model.getId());
diff --git a/server-spi-private/src/main/java/org/keycloak/models/utils/RepresentationToModel.java b/server-spi-private/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
index e102d3dad2..4a4b4fbfc3 100755
--- a/server-spi-private/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
+++ b/server-spi-private/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
@@ -2171,13 +2171,13 @@ public class RepresentationToModel {
private static void updateResources(Set resourceIds, Policy policy, StoreFactory storeFactory) {
if (resourceIds != null) {
if (resourceIds.isEmpty()) {
- for (Scope scope : new HashSet(policy.getScopes())) {
- policy.removeScope(scope);
+ for (Resource resource : new HashSet<>(policy.getResources())) {
+ policy.removeResource(resource);
}
}
for (String resourceId : resourceIds) {
boolean hasResource = false;
- for (Resource resourceModel : new HashSet(policy.getResources())) {
+ for (Resource resourceModel : new HashSet<>(policy.getResources())) {
if (resourceModel.getId().equals(resourceId) || resourceModel.getName().equals(resourceId)) {
hasResource = true;
}
@@ -2196,7 +2196,7 @@ public class RepresentationToModel {
}
}
- for (Resource resourceModel : new HashSet(policy.getResources())) {
+ for (Resource resourceModel : new HashSet<>(policy.getResources())) {
boolean hasResource = false;
for (String resourceId : resourceIds) {
diff --git a/server-spi-private/src/main/java/org/keycloak/scripting/EvaluatableScriptAdapter.java b/server-spi-private/src/main/java/org/keycloak/scripting/EvaluatableScriptAdapter.java
new file mode 100644
index 0000000000..2a76add926
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/scripting/EvaluatableScriptAdapter.java
@@ -0,0 +1,14 @@
+package org.keycloak.scripting;
+
+import org.keycloak.models.ScriptModel;
+
+/**
+ * Wraps a {@link ScriptModel} so it can be evaluated with custom bindings.
+ *
+ * @author Jay Anslow
+ */
+public interface EvaluatableScriptAdapter {
+ ScriptModel getScriptModel();
+
+ Object eval(ScriptBindingsConfigurer bindingsConfigurer) throws ScriptExecutionException;
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/scripting/InvocableScriptAdapter.java b/server-spi-private/src/main/java/org/keycloak/scripting/InvocableScriptAdapter.java
index c3859aba35..17bb4a1822 100644
--- a/server-spi-private/src/main/java/org/keycloak/scripting/InvocableScriptAdapter.java
+++ b/server-spi-private/src/main/java/org/keycloak/scripting/InvocableScriptAdapter.java
@@ -56,7 +56,7 @@ public class InvocableScriptAdapter implements Invocable {
}
this.scriptModel = scriptModel;
- this.scriptEngine = loadScriptIntoEngine(scriptModel, scriptEngine);
+ this.scriptEngine = scriptEngine;
}
@Override
@@ -101,17 +101,6 @@ public class InvocableScriptAdapter implements Invocable {
return candidate != null;
}
- private ScriptEngine loadScriptIntoEngine(ScriptModel script, ScriptEngine engine) {
-
- try {
- engine.eval(script.getCode());
- } catch (ScriptException se) {
- throw new ScriptExecutionException(script, se);
- }
-
- return engine;
- }
-
private Invocable getInvocableEngine() {
return (Invocable) scriptEngine;
}
diff --git a/server-spi-private/src/main/java/org/keycloak/scripting/ScriptingProvider.java b/server-spi-private/src/main/java/org/keycloak/scripting/ScriptingProvider.java
index 67bad5a9c9..ef2990f6ca 100644
--- a/server-spi-private/src/main/java/org/keycloak/scripting/ScriptingProvider.java
+++ b/server-spi-private/src/main/java/org/keycloak/scripting/ScriptingProvider.java
@@ -38,6 +38,14 @@ public interface ScriptingProvider extends Provider {
*/
InvocableScriptAdapter prepareInvocableScript(ScriptModel scriptModel, ScriptBindingsConfigurer bindingsConfigurer);
+ /**
+ * Returns an {@link EvaluatableScriptAdapter} based on the given {@link ScriptModel}.
+ *
The {@code EvaluatableScriptAdapter} wraps a dedicated {@link ScriptEngine} that was populated with empty bindings.
+ *
+ * @param scriptModel the scriptModel to wrap
+ */
+ EvaluatableScriptAdapter prepareEvaluatableScript(ScriptModel scriptModel);
+
/**
* Creates a new {@link ScriptModel} instance.
*
diff --git a/server-spi/src/main/java/org/keycloak/component/ComponentFactory.java b/server-spi/src/main/java/org/keycloak/component/ComponentFactory.java
index 695637fc0a..b4c6f44610 100644
--- a/server-spi/src/main/java/org/keycloak/component/ComponentFactory.java
+++ b/server-spi/src/main/java/org/keycloak/component/ComponentFactory.java
@@ -79,6 +79,18 @@ public interface ComponentFactory ex
}
+ /**
+ * Called before the component is removed.
+ *
+ * @param session
+ * @param realm
+ * @param model model of the component, which is going to be removed
+ */
+ default
+ void preRemove(KeycloakSession session, RealmModel realm, ComponentModel model) {
+
+ }
+
/**
* These are config properties that are common across all implementation of this component type
*
diff --git a/server-spi/src/main/java/org/keycloak/models/ClientInitialAccessModel.java b/server-spi/src/main/java/org/keycloak/models/ClientInitialAccessModel.java
old mode 100755
new mode 100644
index 149b6aab52..24b5546de4
--- a/server-spi/src/main/java/org/keycloak/models/ClientInitialAccessModel.java
+++ b/server-spi/src/main/java/org/keycloak/models/ClientInitialAccessModel.java
@@ -20,20 +20,55 @@ package org.keycloak.models;
/**
* @author Stian Thorgersen
*/
-public interface ClientInitialAccessModel {
+public class ClientInitialAccessModel {
- String getId();
+ private String id;
- RealmModel getRealm();
+ private int timestamp;
- int getTimestamp();
+ private int expiration;
- int getExpiration();
+ private int count;
- int getCount();
+ private int remainingCount;
- int getRemainingCount();
+ public String getId() {
+ return id;
+ }
- void decreaseRemainingCount();
+ public void setId(String id) {
+ this.id = id;
+ }
+ public int getTimestamp() {
+ return timestamp;
+ }
+
+ public void setTimestamp(int timestamp) {
+ this.timestamp = timestamp;
+ }
+
+ public int getExpiration() {
+ return expiration;
+ }
+
+ public void setExpiration(int expiration) {
+ this.expiration = expiration;
+ }
+
+ public int getCount() {
+ return count;
+ }
+
+ public void setCount(int count) {
+ this.count = count;
+ }
+
+ public int getRemainingCount() {
+ return remainingCount;
+ }
+
+ public void setRemainingCount(int remainingCount) {
+ this.remainingCount = remainingCount;
+ }
}
diff --git a/server-spi/src/main/java/org/keycloak/models/RealmProvider.java b/server-spi/src/main/java/org/keycloak/models/RealmProvider.java
index 72c11ad701..3b043fc7ad 100755
--- a/server-spi/src/main/java/org/keycloak/models/RealmProvider.java
+++ b/server-spi/src/main/java/org/keycloak/models/RealmProvider.java
@@ -98,4 +98,11 @@ public interface RealmProvider extends Provider {
List getRealms();
boolean removeRealm(String id);
void close();
+
+ ClientInitialAccessModel createClientInitialAccessModel(RealmModel realm, int expiration, int count);
+ ClientInitialAccessModel getClientInitialAccessModel(RealmModel realm, String id);
+ void removeClientInitialAccessModel(RealmModel realm, String id);
+ List listClientInitialAccess(RealmModel realm);
+ void removeExpiredClientInitialAccess();
+ void decreaseRemainingCount(RealmModel realm, ClientInitialAccessModel clientInitialAccess); // Separate provider method to ensure we decrease remainingCount atomically instead of doing classic update
}
diff --git a/server-spi/src/main/java/org/keycloak/models/UserSessionProvider.java b/server-spi/src/main/java/org/keycloak/models/UserSessionProvider.java
index d474e89a71..848c098e70 100755
--- a/server-spi/src/main/java/org/keycloak/models/UserSessionProvider.java
+++ b/server-spi/src/main/java/org/keycloak/models/UserSessionProvider.java
@@ -72,11 +72,6 @@ public interface UserSessionProvider extends Provider {
/** Triggered by persister during pre-load. It optionally imports authenticatedClientSessions too if requested. Otherwise the imported UserSession will have empty list of AuthenticationSessionModel **/
UserSessionModel importUserSession(UserSessionModel persistentUserSession, boolean offline, boolean importAuthenticatedClientSessions);
- ClientInitialAccessModel createClientInitialAccessModel(RealmModel realm, int expiration, int count);
- ClientInitialAccessModel getClientInitialAccessModel(RealmModel realm, String id);
- void removeClientInitialAccessModel(RealmModel realm, String id);
- List listClientInitialAccess(RealmModel realm);
-
void close();
}
diff --git a/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java b/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
index 23d06e3d0f..af7d2f7a3c 100755
--- a/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
+++ b/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
@@ -471,6 +471,7 @@ public class AuthenticationProcessor {
LoginFormsProvider provider = getSession().getProvider(LoginFormsProvider.class)
.setUser(getUser())
.setActionUri(action)
+ .setExecution(getExecution().getId())
.setFormData(request.getDecodedFormParameters())
.setClientSessionCode(accessCode);
if (getForwardedErrorMessage() != null) {
diff --git a/services/src/main/java/org/keycloak/authentication/FormAuthenticationFlow.java b/services/src/main/java/org/keycloak/authentication/FormAuthenticationFlow.java
index 82c12ec500..575677d0b3 100755
--- a/services/src/main/java/org/keycloak/authentication/FormAuthenticationFlow.java
+++ b/services/src/main/java/org/keycloak/authentication/FormAuthenticationFlow.java
@@ -270,6 +270,7 @@ public class FormAuthenticationFlow implements AuthenticationFlow {
URI actionUrl = getActionUrl(executionId, code);
LoginFormsProvider form = processor.getSession().getProvider(LoginFormsProvider.class)
.setActionUri(actionUrl)
+ .setExecution(executionId)
.setClientSessionCode(code)
.setFormData(formData)
.setErrors(errors);
diff --git a/services/src/main/java/org/keycloak/authentication/RequiredActionContextResult.java b/services/src/main/java/org/keycloak/authentication/RequiredActionContextResult.java
index 1d9475a80d..3afb34ce8e 100755
--- a/services/src/main/java/org/keycloak/authentication/RequiredActionContextResult.java
+++ b/services/src/main/java/org/keycloak/authentication/RequiredActionContextResult.java
@@ -137,11 +137,15 @@ public class RequiredActionContextResult implements RequiredActionContext {
ClientModel client = authenticationSession.getClient();
return LoginActionsService.requiredActionProcessor(getUriInfo())
.queryParam(OAuth2Constants.CODE, code)
- .queryParam(Constants.EXECUTION, factory.getId())
+ .queryParam(Constants.EXECUTION, getExecution())
.queryParam(Constants.CLIENT_ID, client.getClientId())
.build(getRealm().getName());
}
+ private String getExecution() {
+ return factory.getId();
+ }
+
@Override
public String generateCode() {
ClientSessionCode accessCode = new ClientSessionCode<>(session, getRealm(), getAuthenticationSession());
@@ -164,6 +168,7 @@ public class RequiredActionContextResult implements RequiredActionContext {
LoginFormsProvider provider = getSession().getProvider(LoginFormsProvider.class)
.setUser(getUser())
.setActionUri(action)
+ .setExecution(getExecution())
.setClientSessionCode(accessCode);
return provider;
}
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 7189b955f6..ca841d02f0 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
@@ -169,6 +169,7 @@ public class IdpEmailVerificationAuthenticator extends AbstractIdpAuthenticator
.setStatus(Response.Status.OK)
.setAttribute(LoginFormsProvider.IDENTITY_PROVIDER_BROKER_CONTEXT, brokerContext)
.setActionUri(action)
+ .setExecution(context.getExecution().getId())
.createIdpLinkEmailPage();
context.forceChallenge(challenge);
}
diff --git a/services/src/main/java/org/keycloak/authorization/admin/AuthorizationService.java b/services/src/main/java/org/keycloak/authorization/admin/AuthorizationService.java
index a4ff868276..bc9dd1f19f 100644
--- a/services/src/main/java/org/keycloak/authorization/admin/AuthorizationService.java
+++ b/services/src/main/java/org/keycloak/authorization/admin/AuthorizationService.java
@@ -23,6 +23,7 @@ import org.keycloak.authorization.AuthorizationProvider;
import org.keycloak.authorization.model.ResourceServer;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
+import org.keycloak.services.resources.admin.AdminEventBuilder;
import org.keycloak.services.resources.admin.RealmAuth;
import javax.ws.rs.Path;
@@ -34,14 +35,14 @@ public class AuthorizationService {
private final RealmAuth auth;
private final ClientModel client;
- private final KeycloakSession session;
private final ResourceServer resourceServer;
private final AuthorizationProvider authorization;
+ private final AdminEventBuilder adminEvent;
- public AuthorizationService(KeycloakSession session, ClientModel client, RealmAuth auth) {
- this.session = session;
+ public AuthorizationService(KeycloakSession session, ClientModel client, RealmAuth auth, AdminEventBuilder adminEvent) {
this.client = client;
this.authorization = session.getProvider(AuthorizationProvider.class);
+ this.adminEvent = adminEvent;
this.resourceServer = this.authorization.getStoreFactory().getResourceServerStore().findByClient(this.client.getId());
this.auth = auth;
@@ -52,16 +53,16 @@ public class AuthorizationService {
@Path("/resource-server")
public ResourceServerService resourceServer() {
- ResourceServerService resource = new ResourceServerService(this.authorization, this.resourceServer, this.client, this.auth);
+ ResourceServerService resource = new ResourceServerService(this.authorization, this.resourceServer, this.client, this.auth, adminEvent);
ResteasyProviderFactory.getInstance().injectProperties(resource);
return resource;
}
- public void enable() {
+ public void enable(boolean newClient) {
if (!isEnabled()) {
- resourceServer().create();
+ resourceServer().create(newClient);
}
}
diff --git a/services/src/main/java/org/keycloak/authorization/admin/PermissionService.java b/services/src/main/java/org/keycloak/authorization/admin/PermissionService.java
index ba4fae255d..ea4677da37 100644
--- a/services/src/main/java/org/keycloak/authorization/admin/PermissionService.java
+++ b/services/src/main/java/org/keycloak/authorization/admin/PermissionService.java
@@ -22,6 +22,7 @@ import java.util.Map;
import org.keycloak.authorization.AuthorizationProvider;
import org.keycloak.authorization.model.Policy;
import org.keycloak.authorization.model.ResourceServer;
+import org.keycloak.services.resources.admin.AdminEventBuilder;
import org.keycloak.services.resources.admin.RealmAuth;
/**
@@ -29,18 +30,18 @@ import org.keycloak.services.resources.admin.RealmAuth;
*/
public class PermissionService extends PolicyService {
- public PermissionService(ResourceServer resourceServer, AuthorizationProvider authorization, RealmAuth auth) {
- super(resourceServer, authorization, auth);
+ public PermissionService(ResourceServer resourceServer, AuthorizationProvider authorization, RealmAuth auth, AdminEventBuilder adminEvent) {
+ super(resourceServer, authorization, auth, adminEvent);
}
@Override
protected PolicyResourceService doCreatePolicyResource(Policy policy) {
- return new PolicyTypeResourceService(policy, resourceServer, authorization, auth);
+ return new PolicyTypeResourceService(policy, resourceServer, authorization, auth, adminEvent);
}
@Override
protected PolicyTypeService doCreatePolicyTypeResource(String type) {
- return new PolicyTypeService(type, resourceServer, authorization, auth) {
+ return new PolicyTypeService(type, resourceServer, authorization, auth, adminEvent) {
@Override
protected List