group permissions
This commit is contained in:
commit
b9f7a43a72
227 changed files with 6087 additions and 2073 deletions
|
@ -35,6 +35,8 @@ import java.util.Map;
|
|||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @author <a href="mailto:brad.culley@spartasystems.com">Brad Culley</a>
|
||||
* @author <a href="mailto:john.ament@spartasystems.com">John D. Ament</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class KeycloakDeployment {
|
||||
|
@ -88,6 +90,7 @@ public class KeycloakDeployment {
|
|||
|
||||
// https://tools.ietf.org/html/rfc7636
|
||||
protected boolean pkce = false;
|
||||
protected boolean ignoreOAuthQueryParameter;
|
||||
|
||||
public KeycloakDeployment() {
|
||||
}
|
||||
|
@ -436,4 +439,11 @@ public class KeycloakDeployment {
|
|||
this.pkce = pkce;
|
||||
}
|
||||
|
||||
public void setIgnoreOAuthQueryParameter(boolean ignoreOAuthQueryParameter) {
|
||||
this.ignoreOAuthQueryParameter = ignoreOAuthQueryParameter;
|
||||
}
|
||||
|
||||
public boolean isOAuthQueryParameterEnabled() {
|
||||
return !this.ignoreOAuthQueryParameter;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,6 +37,8 @@ import java.security.PublicKey;
|
|||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @author <a href="mailto:brad.culley@spartasystems.com">Brad Culley</a>
|
||||
* @author <a href="mailto:john.ament@spartasystems.com">John D. Ament</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class KeycloakDeploymentBuilder {
|
||||
|
@ -113,6 +115,7 @@ public class KeycloakDeploymentBuilder {
|
|||
deployment.setTokenMinimumTimeToLive(adapterConfig.getTokenMinimumTimeToLive());
|
||||
deployment.setMinTimeBetweenJwksRequests(adapterConfig.getMinTimeBetweenJwksRequests());
|
||||
deployment.setPublicKeyCacheTtl(adapterConfig.getPublicKeyCacheTtl());
|
||||
deployment.setIgnoreOAuthQueryParameter(adapterConfig.isIgnoreOAuthQueryParameter());
|
||||
|
||||
if (realmKeyPem == null && adapterConfig.isBearerOnly() && adapterConfig.getAuthServerUrl() == null) {
|
||||
throw new IllegalArgumentException("For bearer auth, you must set the realm-public-key or auth-server-url");
|
||||
|
|
|
@ -22,6 +22,8 @@ import org.keycloak.adapters.spi.HttpFacade;
|
|||
|
||||
/**
|
||||
* @author <a href="mailto:froehlich.ch@gmail.com">Christian Froehlich</a>
|
||||
* @author <a href="mailto:brad.culley@spartasystems.com">Brad Culley</a>
|
||||
* @author <a href="mailto:john.ament@spartasystems.com">John D. Ament</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class QueryParamterTokenRequestAuthenticator extends BearerTokenRequestAuthenticator {
|
||||
|
@ -33,6 +35,9 @@ public class QueryParamterTokenRequestAuthenticator extends BearerTokenRequestAu
|
|||
}
|
||||
|
||||
public AuthOutcome authenticate(HttpFacade exchange) {
|
||||
if(!deployment.isOAuthQueryParameterEnabled()) {
|
||||
return AuthOutcome.NOT_ATTEMPTED;
|
||||
}
|
||||
tokenString = null;
|
||||
tokenString = getAccessTokenFromQueryParamter(exchange);
|
||||
if (tokenString == null || tokenString.trim().isEmpty()) {
|
||||
|
|
|
@ -29,10 +29,13 @@ import org.keycloak.common.util.PemUtils;
|
|||
import org.keycloak.enums.TokenStore;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
* @author <a href="mailto:brad.culley@spartasystems.com">Brad Culley</a>
|
||||
* @author <a href="mailto:john.ament@spartasystems.com">John D. Ament</a>
|
||||
*/
|
||||
public class KeycloakDeploymentBuilderTest {
|
||||
|
||||
|
@ -58,6 +61,7 @@ public class KeycloakDeploymentBuilderTest {
|
|||
assertTrue(deployment.isPublicClient());
|
||||
assertTrue(deployment.isEnableBasicAuth());
|
||||
assertTrue(deployment.isExposeToken());
|
||||
assertFalse(deployment.isOAuthQueryParameterEnabled());
|
||||
assertEquals("234234-234234-234234", deployment.getResourceCredentials().get("secret"));
|
||||
assertEquals(ClientIdAndSecretCredentialsProvider.PROVIDER_ID, deployment.getClientAuthenticator().getId());
|
||||
assertEquals(20, ((ThreadSafeClientConnManager) deployment.getClient().getConnectionManager()).getMaxTotal());
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.adapters;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:brad.culley@spartasystems.com">Brad Culley</a>
|
||||
* @author <a href="mailto:john.ament@spartasystems.com">John D. Ament</a>
|
||||
*/
|
||||
public class KeycloakDeploymentTest {
|
||||
@Test
|
||||
public void shouldNotEnableOAuthQueryParamWhenIgnoreIsTrue() {
|
||||
KeycloakDeployment keycloakDeployment = new KeycloakDeployment();
|
||||
keycloakDeployment.setIgnoreOAuthQueryParameter(true);
|
||||
assertFalse(keycloakDeployment.isOAuthQueryParameterEnabled());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldEnableOAuthQueryParamWhenIgnoreIsFalse() {
|
||||
KeycloakDeployment keycloakDeployment = new KeycloakDeployment();
|
||||
keycloakDeployment.setIgnoreOAuthQueryParameter(false);
|
||||
assertTrue(keycloakDeployment.isOAuthQueryParameterEnabled());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldEnableOAuthQueryParamWhenIgnoreNotSet() {
|
||||
KeycloakDeployment keycloakDeployment = new KeycloakDeployment();
|
||||
|
||||
assertTrue(keycloakDeployment.isOAuthQueryParameterEnabled());
|
||||
}
|
||||
}
|
|
@ -32,5 +32,6 @@
|
|||
"principal-attribute": "email",
|
||||
"token-minimum-time-to-live": 10,
|
||||
"min-time-between-jwks-requests": 20,
|
||||
"public-key-cache-ttl": 120
|
||||
"public-key-cache-ttl": 120,
|
||||
"ignore-oauth-query-parameter": true
|
||||
}
|
|
@ -33,6 +33,7 @@ import org.keycloak.adapters.tomcat.KeycloakAuthenticatorValve;
|
|||
import org.keycloak.adapters.undertow.KeycloakServletExtension;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
|
||||
import org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainer;
|
||||
import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer;
|
||||
|
@ -60,6 +61,7 @@ import java.util.Set;
|
|||
@Configuration
|
||||
@ConditionalOnWebApplication
|
||||
@EnableConfigurationProperties(KeycloakSpringBootProperties.class)
|
||||
@ConditionalOnProperty(value = "keycloak.enabled", matchIfMissing = true)
|
||||
public class KeycloakAutoConfiguration {
|
||||
|
||||
private KeycloakSpringBootProperties keycloakProperties;
|
||||
|
|
|
@ -34,6 +34,11 @@ public class KeycloakSpringBootProperties extends AdapterConfig {
|
|||
@JsonIgnore
|
||||
private Map config = new HashMap();
|
||||
|
||||
/**
|
||||
* Allow enabling of Keycloak Spring Boot adapter by configuration.
|
||||
*/
|
||||
private boolean enabled = true;
|
||||
|
||||
public Map getConfig() {
|
||||
return config;
|
||||
}
|
||||
|
@ -43,6 +48,14 @@ public class KeycloakSpringBootProperties extends AdapterConfig {
|
|||
*/
|
||||
private List<SecurityConstraint> securityConstraints = new ArrayList<SecurityConstraint>();
|
||||
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* This matches security-constraint of the servlet spec
|
||||
*/
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.adapters.springsecurity.authentication;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
|
||||
|
||||
/**
|
||||
* To return the forbidden code with the corresponding message.
|
||||
*
|
||||
* @author emilienbondu
|
||||
*
|
||||
*/
|
||||
public class KeycloakAuthenticationFailureHandler implements AuthenticationFailureHandler {
|
||||
|
||||
@Override
|
||||
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
|
||||
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unable to authenticate using the Authorization header");
|
||||
}
|
||||
}
|
|
@ -17,6 +17,13 @@
|
|||
|
||||
package org.keycloak.adapters.springsecurity.filter;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.keycloak.adapters.AdapterDeploymentContext;
|
||||
import org.keycloak.adapters.AdapterTokenStore;
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
|
@ -25,7 +32,7 @@ import org.keycloak.adapters.spi.AuthChallenge;
|
|||
import org.keycloak.adapters.spi.AuthOutcome;
|
||||
import org.keycloak.adapters.spi.HttpFacade;
|
||||
import org.keycloak.adapters.springsecurity.KeycloakAuthenticationException;
|
||||
import org.keycloak.adapters.springsecurity.authentication.KeycloakAuthenticationEntryPoint;
|
||||
import org.keycloak.adapters.springsecurity.authentication.KeycloakAuthenticationFailureHandler;
|
||||
import org.keycloak.adapters.springsecurity.authentication.SpringSecurityRequestAuthenticator;
|
||||
import org.keycloak.adapters.springsecurity.facade.SimpleHttpFacade;
|
||||
import org.keycloak.adapters.springsecurity.token.AdapterTokenStoreFactory;
|
||||
|
@ -41,19 +48,12 @@ import org.springframework.security.core.Authentication;
|
|||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
|
||||
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
|
||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.OrRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.RequestHeaderRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Provides a Keycloak authentication processing filter.
|
||||
*
|
||||
|
@ -61,17 +61,15 @@ import java.io.IOException;
|
|||
* @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 requests to the {@link KeycloakAuthenticationEntryPoint#DEFAULT_LOGIN_URI default login URI}
|
||||
* and any request with a <code>Authorization</code> header.
|
||||
* Request matcher that matches all requests.
|
||||
*/
|
||||
public static final RequestMatcher DEFAULT_REQUEST_MATCHER =
|
||||
new OrRequestMatcher(new AntPathRequestMatcher(DEFAULT_LOGIN_URL), new RequestHeaderRequestMatcher(AUTHORIZATION_HEADER));
|
||||
private static RequestMatcher DEFAULT_REQUEST_MATCHER = new AntPathRequestMatcher("/**");
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(KeycloakAuthenticationProcessingFilter.class);
|
||||
|
||||
|
@ -89,7 +87,7 @@ public class KeycloakAuthenticationProcessingFilter extends AbstractAuthenticati
|
|||
*/
|
||||
public KeycloakAuthenticationProcessingFilter(AuthenticationManager authenticationManager) {
|
||||
this(authenticationManager, DEFAULT_REQUEST_MATCHER);
|
||||
setAuthenticationFailureHandler(new SimpleUrlAuthenticationFailureHandler(DEFAULT_LOGIN_URL));
|
||||
setAuthenticationFailureHandler(new KeycloakAuthenticationFailureHandler());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -140,7 +138,18 @@ public class KeycloakAuthenticationProcessingFilter extends AbstractAuthenticati
|
|||
log.debug("Auth outcome: {}", result);
|
||||
|
||||
if (AuthOutcome.FAILED.equals(result)) {
|
||||
throw new KeycloakAuthenticationException("Auth outcome: " + result);
|
||||
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();
|
||||
if (challenge != null) {
|
||||
challenge.challenge(facade);
|
||||
}
|
||||
throw new KeycloakAuthenticationException("Authorization header not found, see WWW-Authenticate header");
|
||||
}
|
||||
|
||||
else if (AuthOutcome.AUTHENTICATED.equals(result)) {
|
||||
|
@ -213,18 +222,6 @@ public class KeycloakAuthenticationProcessingFilter extends AbstractAuthenticati
|
|||
@Override
|
||||
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
|
||||
AuthenticationException failed) throws IOException, ServletException {
|
||||
|
||||
if (this.isBearerTokenRequest(request)) {
|
||||
SecurityContextHolder.clearContext();
|
||||
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unable to authenticate bearer token");
|
||||
return;
|
||||
}
|
||||
else if (this.isBasicAuthRequest(request)) {
|
||||
SecurityContextHolder.clearContext();
|
||||
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unable to authenticate with basic authentication");
|
||||
return;
|
||||
}
|
||||
|
||||
super.unsuccessfulAuthentication(request, response, failed);
|
||||
}
|
||||
|
||||
|
|
|
@ -27,6 +27,7 @@ import org.keycloak.adapters.OidcKeycloakAccount;
|
|||
import org.keycloak.adapters.spi.HttpFacade;
|
||||
import org.keycloak.adapters.springsecurity.KeycloakAuthenticationException;
|
||||
import org.keycloak.adapters.springsecurity.account.KeycloakRole;
|
||||
import org.keycloak.adapters.springsecurity.authentication.KeycloakAuthenticationFailureHandler;
|
||||
import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken;
|
||||
import org.keycloak.common.enums.SslRequired;
|
||||
import org.keycloak.common.util.KeycloakUriBuilder;
|
||||
|
@ -90,6 +91,8 @@ public class KeycloakAuthenticationProcessingFilterTest {
|
|||
@Mock
|
||||
private AuthenticationFailureHandler failureHandler;
|
||||
|
||||
private KeycloakAuthenticationFailureHandler keycloakFailureHandler;
|
||||
|
||||
@Mock
|
||||
private OidcKeycloakAccount keycloakAccount;
|
||||
|
||||
|
@ -106,6 +109,7 @@ public class KeycloakAuthenticationProcessingFilterTest {
|
|||
MockitoAnnotations.initMocks(this);
|
||||
request = spy(new MockHttpServletRequest());
|
||||
filter = new KeycloakAuthenticationProcessingFilter(authenticationManager);
|
||||
keycloakFailureHandler = new KeycloakAuthenticationFailureHandler();
|
||||
|
||||
filter.setApplicationContext(applicationContext);
|
||||
filter.setAuthenticationSuccessHandler(successHandler);
|
||||
|
@ -155,11 +159,13 @@ 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"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test(expected = KeycloakAuthenticationException.class)
|
||||
public void testAttemptAuthenticationWithInvalidToken() throws Exception {
|
||||
|
@ -210,8 +216,7 @@ public class KeycloakAuthenticationProcessingFilterTest {
|
|||
AuthenticationException exception = new BadCredentialsException("OOPS");
|
||||
this.setBearerAuthHeader(request);
|
||||
filter.unsuccessfulAuthentication(request, response, exception);
|
||||
verify(response).sendError(eq(HttpServletResponse.SC_UNAUTHORIZED), anyString());
|
||||
verify(failureHandler, never()).onAuthenticationFailure(any(HttpServletRequest.class), any(HttpServletResponse.class),
|
||||
verify(failureHandler).onAuthenticationFailure(any(HttpServletRequest.class), any(HttpServletResponse.class),
|
||||
any(AuthenticationException.class));
|
||||
}
|
||||
|
||||
|
@ -220,11 +225,19 @@ public class KeycloakAuthenticationProcessingFilterTest {
|
|||
AuthenticationException exception = new BadCredentialsException("OOPS");
|
||||
this.setBasicAuthHeader(request);
|
||||
filter.unsuccessfulAuthentication(request, response, exception);
|
||||
verify(response).sendError(eq(HttpServletResponse.SC_UNAUTHORIZED), anyString());
|
||||
verify(failureHandler, never()).onAuthenticationFailure(any(HttpServletRequest.class), any(HttpServletResponse.class),
|
||||
verify(failureHandler).onAuthenticationFailure(any(HttpServletRequest.class), any(HttpServletResponse.class),
|
||||
any(AuthenticationException.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDefaultFailureHanlder() throws Exception {
|
||||
AuthenticationException exception = new BadCredentialsException("OOPS");
|
||||
filter.setAuthenticationFailureHandler(keycloakFailureHandler);
|
||||
filter.unsuccessfulAuthentication(request, response, exception);
|
||||
|
||||
verify(response).sendError(eq(HttpServletResponse.SC_UNAUTHORIZED), any(String.class));
|
||||
}
|
||||
|
||||
@Test(expected = UnsupportedOperationException.class)
|
||||
public void testSetAllowSessionCreation() throws Exception {
|
||||
filter.setAllowSessionCreation(true);
|
||||
|
|
|
@ -180,6 +180,13 @@ public class SharedAttributeDefinitons {
|
|||
.setDefaultValue(new ModelNode(false))
|
||||
.build();
|
||||
|
||||
protected static final SimpleAttributeDefinition IGNORE_OAUTH_QUERY_PARAMETER =
|
||||
new SimpleAttributeDefinitionBuilder("ignore-oauth-query-parameter", ModelType.BOOLEAN, true)
|
||||
.setXmlName("ignore-oauth-query-parameter")
|
||||
.setAllowExpression(true)
|
||||
.setDefaultValue(new ModelNode(false))
|
||||
.build();
|
||||
|
||||
protected static final List<SimpleAttributeDefinition> ATTRIBUTES = new ArrayList<SimpleAttributeDefinition>();
|
||||
static {
|
||||
ATTRIBUTES.add(REALM_PUBLIC_KEY);
|
||||
|
@ -206,6 +213,7 @@ public class SharedAttributeDefinitons {
|
|||
ATTRIBUTES.add(TOKEN_STORE);
|
||||
ATTRIBUTES.add(PRINCIPAL_ATTRIBUTE);
|
||||
ATTRIBUTES.add(AUTODETECT_BEARER_ONLY);
|
||||
ATTRIBUTES.add(IGNORE_OAUTH_QUERY_PARAMETER);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -48,6 +48,7 @@ keycloak.realm.register-node-period=how often to re-register node
|
|||
keycloak.realm.token-store=cookie or session storage for auth session data
|
||||
keycloak.realm.principal-attribute=token attribute to use to set Principal name
|
||||
keycloak.realm.autodetect-bearer-only=autodetect bearer-only requests
|
||||
keycloak.realm.ignore-oauth-query-parameter=disable query parameter parsing for access_token
|
||||
|
||||
keycloak.secure-deployment=A deployment secured by Keycloak
|
||||
keycloak.secure-deployment.add=Add a deployment to be secured by Keycloak
|
||||
|
@ -86,6 +87,7 @@ keycloak.secure-deployment.turn-off-change-session-id-on-login=The session id is
|
|||
keycloak.secure-deployment.token-minimum-time-to-live=The adapter will refresh the token if the current token is expired OR will expire in 'token-minimum-time-to-live' seconds or less
|
||||
keycloak.secure-deployment.min-time-between-jwks-requests=If adapter recognize token signed by unknown public key, it will try to download new public key from keycloak server. However it won't try to download if already tried it in less than 'min-time-between-jwks-requests' seconds
|
||||
keycloak.secure-deployment.autodetect-bearer-only=autodetect bearer-only requests
|
||||
keycloak.secure-deployment.ignore-oauth-query-parameter=disable query parameter parsing for access_token
|
||||
|
||||
keycloak.secure-deployment.credential=Credential value
|
||||
|
||||
|
|
|
@ -67,6 +67,7 @@
|
|||
<xs:element name="token-store" type="xs:string" minOccurs="0" maxOccurs="1"/>
|
||||
<xs:element name="principal-attribute" type="xs:string" minOccurs="0" maxOccurs="1"/>
|
||||
<xs:element name="autodetect-bearer-only" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
|
||||
<xs:element name="ignore-oauth-query-parameter" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
|
||||
</xs:all>
|
||||
<xs:attribute name="name" type="xs:string" use="required">
|
||||
<xs:annotation>
|
||||
|
@ -111,6 +112,7 @@
|
|||
<xs:element name="token-minimum-time-to-live" type="xs:integer" minOccurs="0" maxOccurs="1"/>
|
||||
<xs:element name="min-time-between-jwks-requests" type="xs:integer" minOccurs="0" maxOccurs="1"/>
|
||||
<xs:element name="autodetect-bearer-only" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
|
||||
<xs:element name="ignore-oauth-query-parameter" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
|
||||
</xs:all>
|
||||
<xs:attribute name="name" type="xs:string" use="required">
|
||||
<xs:annotation>
|
||||
|
|
|
@ -179,6 +179,12 @@ public class SharedAttributeDefinitons {
|
|||
.setDefaultValue(new ModelNode(false))
|
||||
.build();
|
||||
|
||||
protected static final SimpleAttributeDefinition IGNORE_OAUTH_QUERY_PARAMETER =
|
||||
new SimpleAttributeDefinitionBuilder("ignore-oauth-query-parameter", ModelType.BOOLEAN, true)
|
||||
.setXmlName("ignore-oauth-query-parameter")
|
||||
.setAllowExpression(true)
|
||||
.setDefaultValue(new ModelNode(false))
|
||||
.build();
|
||||
|
||||
|
||||
|
||||
|
@ -209,6 +215,7 @@ public class SharedAttributeDefinitons {
|
|||
ATTRIBUTES.add(TOKEN_STORE);
|
||||
ATTRIBUTES.add(PRINCIPAL_ATTRIBUTE);
|
||||
ATTRIBUTES.add(AUTODETECT_BEARER_ONLY);
|
||||
ATTRIBUTES.add(IGNORE_OAUTH_QUERY_PARAMETER);
|
||||
}
|
||||
|
||||
private static boolean isSet(ModelNode attributes, SimpleAttributeDefinition def) {
|
||||
|
|
|
@ -48,6 +48,7 @@ keycloak.realm.register-node-period=how often to re-register node
|
|||
keycloak.realm.token-store=cookie or session storage for auth session data
|
||||
keycloak.realm.principal-attribute=token attribute to use to set Principal name
|
||||
keycloak.realm.autodetect-bearer-only=autodetect bearer-only requests
|
||||
keycloak.realm.ignore-oauth-query-parameter=disable query parameter parsing for access_token
|
||||
|
||||
keycloak.secure-deployment=A deployment secured by Keycloak
|
||||
keycloak.secure-deployment.add=Add a deployment to be secured by Keycloak
|
||||
|
@ -86,6 +87,7 @@ keycloak.secure-deployment.principal-attribute=token attribute to use to set Pri
|
|||
keycloak.secure-deployment.turn-off-change-session-id-on-login=The session id is changed by default on a successful login. Change this to true if you want to turn this off
|
||||
keycloak.secure-deployment.token-minimum-time-to-live=The adapter will refresh the token if the current token is expired OR will expire in 'token-minimum-time-to-live' seconds or less
|
||||
keycloak.secure-deployment.min-time-between-jwks-requests=If adapter recognize token signed by unknown public key, it will try to download new public key from keycloak server. However it won't try to download if already tried it in less than 'min-time-between-jwks-requests' seconds
|
||||
keycloak.secure-deployment.ignore-oauth-query-parameter=disable query parameter parsing for access_token
|
||||
|
||||
keycloak.secure-deployment.credential=Credential value
|
||||
|
||||
|
|
|
@ -67,6 +67,7 @@
|
|||
<xs:element name="token-store" type="xs:string" minOccurs="0" maxOccurs="1"/>
|
||||
<xs:element name="principal-attribute" type="xs:string" minOccurs="0" maxOccurs="1"/>
|
||||
<xs:element name="autodetect-bearer-only" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
|
||||
<xs:element name="ignore-oauth-query-parameter" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
|
||||
</xs:all>
|
||||
<xs:attribute name="name" type="xs:string" use="required">
|
||||
<xs:annotation>
|
||||
|
@ -111,6 +112,7 @@
|
|||
<xs:element name="token-minimum-time-to-live" type="xs:integer" minOccurs="0" maxOccurs="1"/>
|
||||
<xs:element name="min-time-between-jwks-requests" type="xs:integer" minOccurs="0" maxOccurs="1"/>
|
||||
<xs:element name="autodetect-bearer-only" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
|
||||
<xs:element name="ignore-oauth-query-parameter" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
|
||||
</xs:all>
|
||||
<xs:attribute name="name" type="xs:string" use="required">
|
||||
<xs:annotation>
|
||||
|
|
|
@ -53,10 +53,12 @@ import org.keycloak.saml.SAML2AuthnRequestBuilder;
|
|||
import org.keycloak.saml.SAMLRequestParser;
|
||||
import org.keycloak.saml.SignatureAlgorithm;
|
||||
import org.keycloak.saml.common.constants.GeneralConstants;
|
||||
import org.keycloak.saml.common.constants.JBossSAMLConstants;
|
||||
import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
|
||||
import org.keycloak.saml.common.exceptions.ConfigurationException;
|
||||
import org.keycloak.saml.common.exceptions.ProcessingException;
|
||||
import org.keycloak.saml.common.util.Base64;
|
||||
import org.keycloak.saml.common.util.DocumentUtil;
|
||||
import org.keycloak.saml.processing.api.saml.v2.sig.SAML2Signature;
|
||||
import org.keycloak.saml.processing.core.saml.v2.common.SAMLDocumentHolder;
|
||||
import org.keycloak.saml.processing.core.saml.v2.util.AssertionUtil;
|
||||
|
@ -74,10 +76,14 @@ import java.security.PublicKey;
|
|||
import java.security.Signature;
|
||||
import java.security.SignatureException;
|
||||
import java.util.*;
|
||||
|
||||
import javax.xml.namespace.QName;
|
||||
|
||||
import org.keycloak.dom.saml.v2.SAML2Object;
|
||||
import org.keycloak.dom.saml.v2.protocol.ExtensionsType;
|
||||
import org.keycloak.rotation.KeyLocator;
|
||||
import org.keycloak.saml.processing.core.util.KeycloakKeySamlExtensionGenerator;
|
||||
import org.keycloak.saml.processing.core.util.XMLEncryptionUtil;
|
||||
|
||||
/**
|
||||
*
|
||||
|
@ -210,7 +216,7 @@ public abstract class AbstractSamlAuthenticationHandler implements SamlAuthentic
|
|||
return AuthOutcome.FAILED;
|
||||
}
|
||||
}
|
||||
return handleLoginResponse((ResponseType) statusResponse, postBinding, onCreateSession);
|
||||
return handleLoginResponse(holder, postBinding, onCreateSession);
|
||||
} finally {
|
||||
sessionStore.setCurrentAction(SamlSessionStore.CurrentAction.NONE);
|
||||
}
|
||||
|
@ -312,7 +318,8 @@ public abstract class AbstractSamlAuthenticationHandler implements SamlAuthentic
|
|||
return false;
|
||||
}
|
||||
|
||||
protected AuthOutcome handleLoginResponse(final ResponseType responseType, boolean postBinding, OnSessionCreated onCreateSession) {
|
||||
protected AuthOutcome handleLoginResponse(SAMLDocumentHolder responseHolder, boolean postBinding, OnSessionCreated onCreateSession) {
|
||||
final ResponseType responseType = (ResponseType) responseHolder.getSamlObject();
|
||||
AssertionType assertion = null;
|
||||
if (! isSuccessfulSamlResponse(responseType) || responseType.getAssertions() == null || responseType.getAssertions().isEmpty()) {
|
||||
challenge = new AuthChallenge() {
|
||||
|
@ -357,11 +364,12 @@ public abstract class AbstractSamlAuthenticationHandler implements SamlAuthentic
|
|||
|
||||
if (deployment.getIDP().getSingleSignOnService().validateAssertionSignature()) {
|
||||
try {
|
||||
validateSamlSignature(new SAMLDocumentHolder(AssertionUtil.asDocument(assertion)), postBinding, GeneralConstants.SAML_RESPONSE_KEY);
|
||||
validateSamlSignature(new SAMLDocumentHolder(buildAssertionDocument(responseHolder, assertion)), postBinding, GeneralConstants.SAML_RESPONSE_KEY);
|
||||
} catch (VerificationException e) {
|
||||
log.error("Failed to verify saml assertion signature", e);
|
||||
|
||||
challenge = new AuthChallenge() {
|
||||
|
||||
@Override
|
||||
public boolean challenge(HttpFacade exchange) {
|
||||
SamlAuthenticationError error = new SamlAuthenticationError(SamlAuthenticationError.Reason.INVALID_SIGNATURE, responseType);
|
||||
|
@ -376,8 +384,24 @@ public abstract class AbstractSamlAuthenticationHandler implements SamlAuthentic
|
|||
}
|
||||
};
|
||||
return AuthOutcome.FAILED;
|
||||
} catch (ProcessingException e) {
|
||||
e.printStackTrace();
|
||||
} catch (Exception e) {
|
||||
log.error("Error processing validation of SAML assertion: " + e.getMessage());
|
||||
challenge = new AuthChallenge() {
|
||||
|
||||
@Override
|
||||
public boolean challenge(HttpFacade exchange) {
|
||||
SamlAuthenticationError error = new SamlAuthenticationError(SamlAuthenticationError.Reason.EXTRACTION_FAILURE);
|
||||
exchange.getRequest().setError(error);
|
||||
exchange.getResponse().sendError(403);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getResponseCode() {
|
||||
return 403;
|
||||
}
|
||||
};
|
||||
return AuthOutcome.FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -480,6 +504,21 @@ 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()));
|
||||
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 AssertionUtil.asDocument(assertion);
|
||||
}
|
||||
|
||||
private String getAttributeValue(Object attrValue) {
|
||||
String value = null;
|
||||
if (attrValue instanceof String) {
|
||||
|
|
|
@ -124,6 +124,11 @@
|
|||
<artifactId>keycloak-spring-boot-starter</artifactId>
|
||||
<version>3.2.0.CR1-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-authz-client</artifactId>
|
||||
<version>3.2.0.CR1-SNAPSHOT</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
|
|
|
@ -24,6 +24,8 @@ import com.fasterxml.jackson.annotation.JsonPropertyOrder;
|
|||
* Configuration for Java based adapters
|
||||
*
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @author <a href="mailto:brad.culley@spartasystems.com">Brad Culley</a>
|
||||
* @author <a href="mailto:john.ament@spartasystems.com">John D. Ament</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
@JsonPropertyOrder({"realm", "realm-public-key", "auth-server-url", "ssl-required",
|
||||
|
@ -38,7 +40,7 @@ import com.fasterxml.jackson.annotation.JsonPropertyOrder;
|
|||
"register-node-at-startup", "register-node-period", "token-store", "principal-attribute",
|
||||
"proxy-url", "turn-off-change-session-id-on-login", "token-minimum-time-to-live",
|
||||
"min-time-between-jwks-requests", "public-key-cache-ttl",
|
||||
"policy-enforcer"
|
||||
"policy-enforcer", "ignore-oauth-query-parameter"
|
||||
})
|
||||
public class AdapterConfig extends BaseAdapterConfig implements AdapterHttpClientConfig {
|
||||
|
||||
|
@ -81,6 +83,8 @@ public class AdapterConfig extends BaseAdapterConfig implements AdapterHttpClien
|
|||
// https://tools.ietf.org/html/rfc7636
|
||||
@JsonProperty("enable-pkce")
|
||||
protected boolean pkce = false;
|
||||
@JsonProperty("ignore-oauth-query-parameter")
|
||||
protected boolean ignoreOAuthQueryParameter = false;
|
||||
|
||||
/**
|
||||
* The Proxy url to use for requests to the auth-server, configurable via the adapter config property {@code proxy-url}.
|
||||
|
@ -257,4 +261,11 @@ public class AdapterConfig extends BaseAdapterConfig implements AdapterHttpClien
|
|||
this.pkce = pkce;
|
||||
}
|
||||
|
||||
public boolean isIgnoreOAuthQueryParameter() {
|
||||
return ignoreOAuthQueryParameter;
|
||||
}
|
||||
|
||||
public void setIgnoreOAuthQueryParameter(boolean ignoreOAuthQueryParameter) {
|
||||
this.ignoreOAuthQueryParameter = ignoreOAuthQueryParameter;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,13 +19,11 @@
|
|||
package org.keycloak.representations.idm.authorization;
|
||||
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.representations.idm.authorization.DecisionEffect;
|
||||
import org.keycloak.representations.idm.authorization.PolicyRepresentation;
|
||||
import org.keycloak.representations.idm.authorization.ResourceRepresentation;
|
||||
import org.keycloak.representations.idm.authorization.ScopeRepresentation;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
|
@ -123,7 +121,7 @@ public class PolicyEvaluationResponse {
|
|||
private PolicyRepresentation policy;
|
||||
private DecisionEffect status;
|
||||
private List<PolicyResultRepresentation> associatedPolicies;
|
||||
private List<ScopeRepresentation> scopes = new ArrayList<>();
|
||||
private Set<String> scopes = new HashSet<>();
|
||||
|
||||
public PolicyRepresentation getPolicy() {
|
||||
return policy;
|
||||
|
@ -162,11 +160,11 @@ public class PolicyEvaluationResponse {
|
|||
return this.policy.equals(policy.getPolicy());
|
||||
}
|
||||
|
||||
public void setScopes(List<ScopeRepresentation> scopes) {
|
||||
public void setScopes(Set<String> scopes) {
|
||||
this.scopes = scopes;
|
||||
}
|
||||
|
||||
public List<ScopeRepresentation> getScopes() {
|
||||
public Set<String> getScopes() {
|
||||
return scopes;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -92,7 +92,9 @@
|
|||
<local-cache name="authenticationSessions"/>
|
||||
<local-cache name="offlineSessions"/>
|
||||
<local-cache name="loginFailures"/>
|
||||
<local-cache name="authorization"/>
|
||||
<local-cache name="authorization">
|
||||
<eviction max-entries="10000" strategy="LRU"/>
|
||||
</local-cache>
|
||||
<local-cache name="actionTokens"/>
|
||||
<local-cache name="work"/>
|
||||
<local-cache name="keys">
|
||||
|
|
|
@ -326,6 +326,10 @@ public class LDAPOperationManager {
|
|||
filter = "(&(objectClass=*)(" + getUuidAttributeName() + LDAPConstants.EQUAL + id + "))";
|
||||
}
|
||||
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.tracef("Using filter for lookup user by LDAP ID: %s", filter);
|
||||
}
|
||||
|
||||
return filter;
|
||||
}
|
||||
|
||||
|
|
|
@ -163,8 +163,13 @@ public interface RealmResource {
|
|||
@POST
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public Response partialImport(PartialImportRepresentation rep);
|
||||
Response partialImport(PartialImportRepresentation rep);
|
||||
|
||||
@Path("partial-export")
|
||||
@POST
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
RealmRepresentation partialExport(@QueryParam("exportGroupsAndRoles") Boolean exportGroupsAndRoles,
|
||||
@QueryParam("exportClients") Boolean exportClients);
|
||||
@Path("authentication")
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
AuthenticationManagementResource flows();
|
||||
|
|
|
@ -17,8 +17,11 @@
|
|||
|
||||
package org.keycloak.client.registration;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.apache.http.impl.client.HttpClients;
|
||||
import org.keycloak.representations.adapters.config.AdapterConfig;
|
||||
|
@ -26,8 +29,8 @@ import org.keycloak.representations.idm.ClientRepresentation;
|
|||
import org.keycloak.representations.oidc.OIDCClientRepresentation;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
|
@ -73,7 +76,7 @@ public class ClientRegistration {
|
|||
|
||||
public ClientRepresentation create(ClientRepresentation client) throws ClientRegistrationException {
|
||||
String content = serialize(client);
|
||||
InputStream resultStream = httpUtil.doPost(content, JSON, JSON, DEFAULT);
|
||||
InputStream resultStream = httpUtil.doPost(content, JSON, UTF_8, JSON, DEFAULT);
|
||||
return deserialize(resultStream, ClientRepresentation.class);
|
||||
}
|
||||
|
||||
|
@ -89,7 +92,7 @@ public class ClientRegistration {
|
|||
|
||||
public ClientRepresentation update(ClientRepresentation client) throws ClientRegistrationException {
|
||||
String content = serialize(client);
|
||||
InputStream resultStream = httpUtil.doPut(content, JSON, JSON, DEFAULT, client.getClientId());
|
||||
InputStream resultStream = httpUtil.doPut(content, JSON, UTF_8, JSON, DEFAULT, client.getClientId());
|
||||
return resultStream != null ? deserialize(resultStream, ClientRepresentation.class) : null;
|
||||
}
|
||||
|
||||
|
@ -129,7 +132,7 @@ public class ClientRegistration {
|
|||
|
||||
public OIDCClientRepresentation create(OIDCClientRepresentation client) throws ClientRegistrationException {
|
||||
String content = serialize(client);
|
||||
InputStream resultStream = httpUtil.doPost(content, JSON, JSON, OIDC);
|
||||
InputStream resultStream = httpUtil.doPost(content, JSON, UTF_8, JSON, OIDC);
|
||||
return deserialize(resultStream, OIDCClientRepresentation.class);
|
||||
}
|
||||
|
||||
|
@ -140,7 +143,7 @@ public class ClientRegistration {
|
|||
|
||||
public OIDCClientRepresentation update(OIDCClientRepresentation client) throws ClientRegistrationException {
|
||||
String content = serialize(client);
|
||||
InputStream resultStream = httpUtil.doPut(content, JSON, JSON, OIDC, client.getClientId());
|
||||
InputStream resultStream = httpUtil.doPut(content, JSON, UTF_8, JSON, OIDC, client.getClientId());
|
||||
return resultStream != null ? deserialize(resultStream, OIDCClientRepresentation.class) : null;
|
||||
}
|
||||
|
||||
|
@ -157,7 +160,7 @@ public class ClientRegistration {
|
|||
public class SAMLClientRegistration {
|
||||
|
||||
public ClientRepresentation create(String entityDescriptor) throws ClientRegistrationException {
|
||||
InputStream resultStream = httpUtil.doPost(entityDescriptor, XML, JSON, SAML);
|
||||
InputStream resultStream = httpUtil.doPost(entityDescriptor, XML, UTF_8, JSON, SAML);
|
||||
return deserialize(resultStream, ClientRepresentation.class);
|
||||
}
|
||||
|
||||
|
|
|
@ -31,6 +31,7 @@ import org.keycloak.common.util.StreamUtil;
|
|||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
|
@ -52,13 +53,13 @@ class HttpUtil {
|
|||
this.auth = auth;
|
||||
}
|
||||
|
||||
InputStream doPost(String content, String contentType, String acceptType, String... path) throws ClientRegistrationException {
|
||||
InputStream doPost(String content, String contentType, Charset charset, String acceptType, String... path) throws ClientRegistrationException {
|
||||
try {
|
||||
HttpPost request = new HttpPost(getUrl(baseUri, path));
|
||||
|
||||
request.setHeader(HttpHeaders.CONTENT_TYPE, contentType);
|
||||
request.setHeader(HttpHeaders.CONTENT_TYPE, contentType(contentType, charset));
|
||||
request.setHeader(HttpHeaders.ACCEPT, acceptType);
|
||||
request.setEntity(new StringEntity(content));
|
||||
request.setEntity(new StringEntity(content, charset));
|
||||
|
||||
addAuth(request);
|
||||
|
||||
|
@ -78,6 +79,10 @@ class HttpUtil {
|
|||
}
|
||||
}
|
||||
|
||||
private String contentType(String contentType, Charset charset) {
|
||||
return contentType + ";charset=" + charset.name();
|
||||
}
|
||||
|
||||
InputStream doGet(String acceptType, String... path) throws ClientRegistrationException {
|
||||
try {
|
||||
HttpGet request = new HttpGet(getUrl(baseUri, path));
|
||||
|
@ -105,13 +110,13 @@ class HttpUtil {
|
|||
}
|
||||
}
|
||||
|
||||
InputStream doPut(String content, String contentType, String acceptType, String... path) throws ClientRegistrationException {
|
||||
InputStream doPut(String content, String contentType, Charset charset, String acceptType, String... path) throws ClientRegistrationException {
|
||||
try {
|
||||
HttpPut request = new HttpPut(getUrl(baseUri, path));
|
||||
|
||||
request.setHeader(HttpHeaders.CONTENT_TYPE, contentType);
|
||||
request.setHeader(HttpHeaders.CONTENT_TYPE, contentType(contentType, charset));
|
||||
request.setHeader(HttpHeaders.ACCEPT, acceptType);
|
||||
request.setEntity(new StringEntity(content));
|
||||
request.setEntity(new StringEntity(content, charset));
|
||||
|
||||
addAuth(request);
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
|
@ -12,16 +13,17 @@
|
|||
<name>keycloak-test-helper</name>
|
||||
<description>Helper library to test application using Keycloak.</description>
|
||||
<packaging>jar</packaging>
|
||||
<properties>
|
||||
<resteasy.client.version>3.0.7.Final</resteasy.client.version>
|
||||
</properties>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-client-registration-api</artifactId>
|
||||
<version>3.2.0.CR1-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-admin-client</artifactId>
|
||||
<version>3.2.0.CR1-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jboss.resteasy</groupId>
|
||||
|
@ -33,5 +35,10 @@
|
|||
<artifactId>resteasy-jackson2-provider</artifactId>
|
||||
<version>3.0.7.Final</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.seleniumhq.selenium</groupId>
|
||||
<artifactId>selenium-java</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.test.page;
|
||||
|
||||
import org.openqa.selenium.WebElement;
|
||||
import org.openqa.selenium.support.FindBy;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bruno@abstractj.org">Bruno Oliveira</a>
|
||||
*/
|
||||
public class IndexPage {
|
||||
|
||||
public static final String UNAUTHORIZED = "401 Unauthorized";
|
||||
|
||||
@FindBy(name = "loginBtn")
|
||||
private WebElement loginButton;
|
||||
|
||||
@FindBy(name = "logoutBtn")
|
||||
private WebElement logoutButton;
|
||||
|
||||
@FindBy(name = "adminBtn")
|
||||
private WebElement adminButton;
|
||||
|
||||
@FindBy(name = "publicBtn")
|
||||
private WebElement publicButton;
|
||||
|
||||
@FindBy(name = "securedBtn")
|
||||
private WebElement securedBtn;
|
||||
|
||||
public void clickLogin() {
|
||||
loginButton.click();
|
||||
}
|
||||
|
||||
public void clickLogout() {
|
||||
logoutButton.click();
|
||||
}
|
||||
|
||||
public void clickAdmin() {
|
||||
adminButton.click();
|
||||
}
|
||||
|
||||
public void clickPublic() {
|
||||
publicButton.click();
|
||||
}
|
||||
|
||||
public void clickSecured() {
|
||||
securedBtn.click();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
* 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.test.page;
|
||||
|
||||
import org.openqa.selenium.WebElement;
|
||||
import org.openqa.selenium.support.FindBy;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class LoginPage {
|
||||
|
||||
@FindBy(id = "username")
|
||||
private WebElement usernameInput;
|
||||
|
||||
@FindBy(id = "password")
|
||||
private WebElement passwordInput;
|
||||
|
||||
@FindBy(id = "totp")
|
||||
private WebElement totp;
|
||||
|
||||
@FindBy(id = "rememberMe")
|
||||
private WebElement rememberMe;
|
||||
|
||||
@FindBy(name = "login")
|
||||
private WebElement submitButton;
|
||||
|
||||
@FindBy(name = "cancel")
|
||||
private WebElement cancelButton;
|
||||
|
||||
@FindBy(linkText = "Register")
|
||||
private WebElement registerLink;
|
||||
|
||||
@FindBy(linkText = "Forgot Password?")
|
||||
private WebElement resetPasswordLink;
|
||||
|
||||
@FindBy(linkText = "Username")
|
||||
private WebElement recoverUsernameLink;
|
||||
|
||||
@FindBy(className = "alert-error")
|
||||
private WebElement loginErrorMessage;
|
||||
|
||||
@FindBy(className = "alert-warning")
|
||||
private WebElement loginWarningMessage;
|
||||
|
||||
@FindBy(className = "alert-success")
|
||||
private WebElement loginSuccessMessage;
|
||||
|
||||
|
||||
@FindBy(className = "alert-info")
|
||||
private WebElement loginInfoMessage;
|
||||
|
||||
@FindBy(className = "instruction")
|
||||
private WebElement instruction;
|
||||
|
||||
|
||||
@FindBy(id = "kc-current-locale-link")
|
||||
private WebElement languageText;
|
||||
|
||||
@FindBy(id = "kc-locale-dropdown")
|
||||
private WebElement localeDropdown;
|
||||
|
||||
public void login(String username, String password) {
|
||||
usernameInput.clear();
|
||||
usernameInput.sendKeys(username);
|
||||
|
||||
passwordInput.clear();
|
||||
passwordInput.sendKeys(password);
|
||||
|
||||
submitButton.click();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
* 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.test.page;
|
||||
|
||||
import org.openqa.selenium.WebElement;
|
||||
import org.openqa.selenium.support.FindBy;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bruno@abstractj.org">Bruno Oliveira</a>
|
||||
*/
|
||||
public class ProfilePage {
|
||||
|
||||
@FindBy(name = "profileBtn")
|
||||
private WebElement profileButton;
|
||||
|
||||
@FindBy(name = "tokenBtn")
|
||||
private WebElement tokenButton;
|
||||
|
||||
@FindBy(name = "logoutBtn")
|
||||
private WebElement logoutButton;
|
||||
|
||||
@FindBy(name = "accountBtn")
|
||||
private WebElement accountButton;
|
||||
|
||||
@FindBy(id = "token-content")
|
||||
private WebElement tokenContent;
|
||||
|
||||
@FindBy(id = "username")
|
||||
private WebElement username;
|
||||
|
||||
public String getUsername() {
|
||||
return username.getText();
|
||||
}
|
||||
|
||||
public void clickProfile() {
|
||||
profileButton.click();
|
||||
}
|
||||
|
||||
public void clickToken() {
|
||||
tokenButton.click();
|
||||
}
|
||||
|
||||
public void clickLogout() {
|
||||
logoutButton.click();
|
||||
}
|
||||
|
||||
public void clickAccount() {
|
||||
accountButton.click();
|
||||
}
|
||||
|
||||
public String getTokenContent() throws Exception {
|
||||
return tokenContent.getText();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -15,6 +15,10 @@
|
|||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-spring-boot-adapter</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-authz-client</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter</artifactId>
|
||||
|
|
|
@ -27,8 +27,6 @@ import org.keycloak.representations.idm.authorization.Logic;
|
|||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
|
@ -49,7 +47,7 @@ public class PolicyAdapter implements Policy, CachedModel<Policy> {
|
|||
@Override
|
||||
public Policy getDelegateForUpdate() {
|
||||
if (updated == null) {
|
||||
cacheSession.registerPolicyInvalidation(cached.getId(), cached.getName(), cached.getResourceServerId());
|
||||
cacheSession.registerPolicyInvalidation(cached.getId(), cached.getName(), cached.getResourcesIds(), cached.getResourceServerId());
|
||||
updated = cacheSession.getPolicyStoreDelegate().findById(cached.getId(), cached.getResourceServerId());
|
||||
if (updated == null) throw new IllegalStateException("Not found in database");
|
||||
}
|
||||
|
@ -98,6 +96,7 @@ public class PolicyAdapter implements Policy, CachedModel<Policy> {
|
|||
@Override
|
||||
public void setName(String name) {
|
||||
getDelegateForUpdate();
|
||||
cacheSession.registerPolicyInvalidation(cached.getId(), name, cached.getResourcesIds(), cached.getResourceServerId());
|
||||
updated.setName(name);
|
||||
|
||||
}
|
||||
|
@ -208,7 +207,6 @@ public class PolicyAdapter implements Policy, CachedModel<Policy> {
|
|||
public void addScope(Scope scope) {
|
||||
getDelegateForUpdate();
|
||||
updated.addScope(scope);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -235,6 +233,9 @@ public class PolicyAdapter implements Policy, CachedModel<Policy> {
|
|||
@Override
|
||||
public void addResource(Resource resource) {
|
||||
getDelegateForUpdate();
|
||||
HashSet<String> resources = new HashSet<>();
|
||||
resources.add(resource.getId());
|
||||
cacheSession.registerPolicyInvalidation(cached.getId(), cached.getName(), resources, cached.getResourceServerId());
|
||||
updated.addResource(resource);
|
||||
|
||||
}
|
||||
|
@ -242,6 +243,9 @@ public class PolicyAdapter implements Policy, CachedModel<Policy> {
|
|||
@Override
|
||||
public void removeResource(Resource resource) {
|
||||
getDelegateForUpdate();
|
||||
HashSet<String> resources = new HashSet<>();
|
||||
resources.add(resource.getId());
|
||||
cacheSession.registerPolicyInvalidation(cached.getId(), cached.getName(), resources, cached.getResourceServerId());
|
||||
updated.removeResource(resource);
|
||||
|
||||
}
|
||||
|
|
|
@ -26,6 +26,8 @@ import java.util.Collections;
|
|||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
|
@ -44,7 +46,7 @@ public class ResourceAdapter implements Resource, CachedModel<Resource> {
|
|||
@Override
|
||||
public Resource getDelegateForUpdate() {
|
||||
if (updated == null) {
|
||||
cacheSession.registerResourceInvalidation(cached.getId(), cached.getName(), cached.getResourceServerId());
|
||||
cacheSession.registerResourceInvalidation(cached.getId(), cached.getName(), cached.getType(), cached.getUri(), cached.getScopesIds(), cached.getResourceServerId());
|
||||
updated = cacheSession.getResourceStoreDelegate().findById(cached.getId(), cached.getResourceServerId());
|
||||
if (updated == null) throw new IllegalStateException("Not found in database");
|
||||
}
|
||||
|
@ -93,6 +95,7 @@ public class ResourceAdapter implements Resource, CachedModel<Resource> {
|
|||
@Override
|
||||
public void setName(String name) {
|
||||
getDelegateForUpdate();
|
||||
cacheSession.registerResourceInvalidation(cached.getId(), name, cached.getType(), cached.getUri(), cached.getScopesIds(), cached.getResourceServerId());
|
||||
updated.setName(name);
|
||||
|
||||
}
|
||||
|
@ -124,8 +127,8 @@ public class ResourceAdapter implements Resource, CachedModel<Resource> {
|
|||
@Override
|
||||
public void setUri(String uri) {
|
||||
getDelegateForUpdate();
|
||||
cacheSession.registerResourceInvalidation(cached.getId(), cached.getName(), cached.getType(), uri, cached.getScopesIds(), cached.getResourceServerId());
|
||||
updated.setUri(uri);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -137,6 +140,7 @@ public class ResourceAdapter implements Resource, CachedModel<Resource> {
|
|||
@Override
|
||||
public void setType(String type) {
|
||||
getDelegateForUpdate();
|
||||
cacheSession.registerResourceInvalidation(cached.getId(), cached.getName(), type, cached.getUri(), cached.getScopesIds(), cached.getResourceServerId());
|
||||
updated.setType(type);
|
||||
|
||||
}
|
||||
|
@ -164,6 +168,7 @@ public class ResourceAdapter implements Resource, CachedModel<Resource> {
|
|||
@Override
|
||||
public void updateScopes(Set<Scope> scopes) {
|
||||
getDelegateForUpdate();
|
||||
cacheSession.registerResourceInvalidation(cached.getId(), cached.getName(), cached.getType(), cached.getUri(), scopes.stream().map(scope1 -> scope1.getId()).collect(Collectors.toSet()), cached.getResourceServerId());
|
||||
updated.updateScopes(scopes);
|
||||
}
|
||||
|
||||
|
|
|
@ -21,7 +21,9 @@ import org.jboss.logging.Logger;
|
|||
import org.keycloak.models.cache.infinispan.CacheManager;
|
||||
import org.keycloak.models.cache.infinispan.RealmCacheManager;
|
||||
import org.keycloak.models.cache.infinispan.authorization.events.AuthorizationCacheInvalidationEvent;
|
||||
import org.keycloak.models.cache.infinispan.authorization.stream.InResourcePredicate;
|
||||
import org.keycloak.models.cache.infinispan.authorization.stream.InResourceServerPredicate;
|
||||
import org.keycloak.models.cache.infinispan.authorization.stream.InScopePredicate;
|
||||
import org.keycloak.models.cache.infinispan.entities.Revisioned;
|
||||
import org.keycloak.models.cache.infinispan.events.InvalidationEvent;
|
||||
|
||||
|
@ -65,28 +67,54 @@ public class StoreFactoryCacheManager extends CacheManager {
|
|||
public void scopeUpdated(String id, String name, String serverId, Set<String> invalidations) {
|
||||
invalidations.add(id);
|
||||
invalidations.add(StoreFactoryCacheSession.getScopeByNameCacheKey(name, serverId));
|
||||
invalidations.add(StoreFactoryCacheSession.getResourceByScopeCacheKey(id, serverId));
|
||||
}
|
||||
|
||||
public void scopeRemoval(String id, String name, String serverId, Set<String> invalidations) {
|
||||
scopeUpdated(id, name, serverId, invalidations);
|
||||
addInvalidations(InScopePredicate.create().scope(id), invalidations);
|
||||
}
|
||||
|
||||
public void resourceUpdated(String id, String name, String serverId, Set<String> invalidations) {
|
||||
public void resourceUpdated(String id, String name, String type, String uri, Set<String> scopes, String serverId, Set<String> invalidations) {
|
||||
invalidations.add(id);
|
||||
invalidations.add(StoreFactoryCacheSession.getResourceByNameCacheKey(name, serverId));
|
||||
|
||||
if (type != null) {
|
||||
invalidations.add(StoreFactoryCacheSession.getResourceByTypeCacheKey(type, serverId));
|
||||
addInvalidations(InResourcePredicate.create().resource(type), invalidations);
|
||||
}
|
||||
|
||||
public void resourceRemoval(String id, String name, String serverId, Set<String> invalidations) {
|
||||
resourceUpdated(id, name, serverId, invalidations);
|
||||
if (uri != null) {
|
||||
invalidations.add(StoreFactoryCacheSession.getResourceByUriCacheKey(uri, serverId));
|
||||
}
|
||||
|
||||
public void policyUpdated(String id, String name, String serverId, Set<String> invalidations) {
|
||||
if (scopes != null) {
|
||||
for (String scope : scopes) {
|
||||
invalidations.add(StoreFactoryCacheSession.getResourceByScopeCacheKey(scope, serverId));
|
||||
addInvalidations(InScopePredicate.create().scope(scope), invalidations);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void resourceRemoval(String id, String name, String type, String uri, String owner, Set<String> scopes, String serverId, Set<String> invalidations) {
|
||||
resourceUpdated(id, name, type, uri, scopes, serverId, invalidations);
|
||||
invalidations.add(StoreFactoryCacheSession.getResourceByOwnerCacheKey(owner, serverId));
|
||||
addInvalidations(InResourcePredicate.create().resource(id), invalidations);
|
||||
}
|
||||
|
||||
public void policyUpdated(String id, String name, Set<String> resources, String serverId, Set<String> invalidations) {
|
||||
invalidations.add(id);
|
||||
invalidations.add(StoreFactoryCacheSession.getPolicyByNameCacheKey(name, serverId));
|
||||
|
||||
if (resources != null) {
|
||||
for (String resource : resources) {
|
||||
invalidations.add(StoreFactoryCacheSession.getPolicyByResource(resource, serverId));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void policyRemoval(String id, String name, String serverId, Set<String> invalidations) {
|
||||
policyUpdated(id, name, serverId, invalidations);
|
||||
public void policyRemoval(String id, String name, Set<String> resources, String serverId, Set<String> invalidations) {
|
||||
policyUpdated(id, name, resources, serverId, invalidations);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -16,6 +16,18 @@
|
|||
*/
|
||||
package org.keycloak.models.cache.infinispan.authorization;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.authorization.model.Policy;
|
||||
import org.keycloak.authorization.model.Resource;
|
||||
|
@ -34,7 +46,12 @@ import org.keycloak.models.cache.infinispan.authorization.entities.CachedResourc
|
|||
import org.keycloak.models.cache.infinispan.authorization.entities.CachedResourceServer;
|
||||
import org.keycloak.models.cache.infinispan.authorization.entities.CachedScope;
|
||||
import org.keycloak.models.cache.infinispan.authorization.entities.PolicyListQuery;
|
||||
import org.keycloak.models.cache.infinispan.authorization.entities.PolicyQuery;
|
||||
import org.keycloak.models.cache.infinispan.authorization.entities.PolicyResourceListQuery;
|
||||
import org.keycloak.models.cache.infinispan.authorization.entities.PolicyScopeListQuery;
|
||||
import org.keycloak.models.cache.infinispan.authorization.entities.ResourceListQuery;
|
||||
import org.keycloak.models.cache.infinispan.authorization.entities.ResourceQuery;
|
||||
import org.keycloak.models.cache.infinispan.authorization.entities.ResourceScopeListQuery;
|
||||
import org.keycloak.models.cache.infinispan.authorization.entities.ResourceServerListQuery;
|
||||
import org.keycloak.models.cache.infinispan.authorization.entities.ScopeListQuery;
|
||||
import org.keycloak.models.cache.infinispan.authorization.events.PolicyRemovedEvent;
|
||||
|
@ -48,12 +65,6 @@ import org.keycloak.models.cache.infinispan.authorization.events.ScopeUpdatedEve
|
|||
import org.keycloak.models.cache.infinispan.events.InvalidationEvent;
|
||||
import org.keycloak.representations.idm.authorization.AbstractPolicyRepresentation;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
|
@ -233,20 +244,20 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider {
|
|||
invalidationEvents.add(ScopeUpdatedEvent.create(id, name, serverId));
|
||||
}
|
||||
|
||||
public void registerResourceInvalidation(String id, String name, String serverId) {
|
||||
cache.resourceUpdated(id, name, serverId, invalidations);
|
||||
public void registerResourceInvalidation(String id, String name, String type, String uri, Set<String> scopes, String serverId) {
|
||||
cache.resourceUpdated(id, name, type, uri, scopes, serverId, invalidations);
|
||||
ResourceAdapter adapter = managedResources.get(id);
|
||||
if (adapter != null) adapter.invalidateFlag();
|
||||
|
||||
invalidationEvents.add(ResourceUpdatedEvent.create(id, name, serverId));
|
||||
invalidationEvents.add(ResourceUpdatedEvent.create(id, name, type, uri, scopes, serverId));
|
||||
}
|
||||
|
||||
public void registerPolicyInvalidation(String id, String name, String serverId) {
|
||||
cache.policyUpdated(id, name, serverId, invalidations);
|
||||
public void registerPolicyInvalidation(String id, String name, Set<String> resources, String serverId) {
|
||||
cache.policyUpdated(id, name, resources, serverId, invalidations);
|
||||
PolicyAdapter adapter = managedPolicies.get(id);
|
||||
if (adapter != null) adapter.invalidateFlag();
|
||||
|
||||
invalidationEvents.add(PolicyUpdatedEvent.create(id, name, serverId));
|
||||
invalidationEvents.add(PolicyUpdatedEvent.create(id, name, resources, serverId));
|
||||
}
|
||||
|
||||
public ResourceServerStore getResourceServerStoreDelegate() {
|
||||
|
@ -277,10 +288,38 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider {
|
|||
return "resource.name." + name + "." + serverId;
|
||||
}
|
||||
|
||||
public static String getResourceByOwnerCacheKey(String owner, String serverId) {
|
||||
return "resource.owner." + owner + "." + serverId;
|
||||
}
|
||||
|
||||
public static String getResourceByTypeCacheKey(String type, String serverId) {
|
||||
return "resource.type." + type + "." + serverId;
|
||||
}
|
||||
|
||||
public static String getResourceByUriCacheKey(String uri, String serverId) {
|
||||
return "resource.uri." + uri + "." + serverId;
|
||||
}
|
||||
|
||||
public static String getResourceByScopeCacheKey(String scopeId, String serverId) {
|
||||
return "resource.scope." + scopeId + "." + serverId;
|
||||
}
|
||||
|
||||
public static String getPolicyByNameCacheKey(String name, String serverId) {
|
||||
return "policy.name." + name + "." + serverId;
|
||||
}
|
||||
|
||||
public static String getPolicyByResource(String resourceId, String serverId) {
|
||||
return "policy.resource." + resourceId + "." + serverId;
|
||||
}
|
||||
|
||||
public static String getPolicyByResourceType(String type, String serverId) {
|
||||
return "policy.resource.type." + type + "." + serverId;
|
||||
}
|
||||
|
||||
public static String getPolicyByScope(String scope, String serverId) {
|
||||
return "policy.scope." + scope + "." + serverId;
|
||||
}
|
||||
|
||||
public StoreFactory getDelegate() {
|
||||
if (delegate != null) return delegate;
|
||||
delegate = session.getProvider(StoreFactory.class);
|
||||
|
@ -451,7 +490,7 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider {
|
|||
@Override
|
||||
public Resource create(String name, ResourceServer resourceServer, String owner) {
|
||||
Resource resource = getResourceStoreDelegate().create(name, resourceServer, owner);
|
||||
registerResourceInvalidation(resource.getId(), resource.getName(), resourceServer.getId());
|
||||
registerResourceInvalidation(resource.getId(), resource.getName(), resource.getType(), resource.getUri(), resource.getScopes().stream().map(scope -> scope.getId()).collect(Collectors.toSet()), resourceServer.getId());
|
||||
return resource;
|
||||
}
|
||||
|
||||
|
@ -462,8 +501,8 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider {
|
|||
if (resource == null) return;
|
||||
|
||||
cache.invalidateObject(id);
|
||||
invalidationEvents.add(ResourceRemovedEvent.create(id, resource.getName(), resource.getResourceServer().getId()));
|
||||
cache.resourceRemoval(id, resource.getName(), resource.getResourceServer().getId(), invalidations);
|
||||
invalidationEvents.add(ResourceRemovedEvent.create(id, resource.getName(), resource.getType(), resource.getUri(), resource.getOwner(), resource.getScopes().stream().map(scope -> scope.getId()).collect(Collectors.toSet()), resource.getResourceServer().getId()));
|
||||
cache.resourceRemoval(id, resource.getName(), resource.getType(), resource.getUri(), resource.getOwner(), resource.getScopes().stream().map(scope -> scope.getId()).collect(Collectors.toSet()), resource.getResourceServer().getId(), invalidations);
|
||||
getResourceStoreDelegate().delete(id);
|
||||
|
||||
}
|
||||
|
@ -498,37 +537,37 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider {
|
|||
public Resource findByName(String name, String resourceServerId) {
|
||||
if (name == null) return null;
|
||||
String cacheKey = getResourceByNameCacheKey(name, resourceServerId);
|
||||
ResourceListQuery query = cache.get(cacheKey, ResourceListQuery.class);
|
||||
if (query != null) {
|
||||
logger.tracev("resource by name cache hit: {0}", name);
|
||||
List<Resource> result = cacheQuery(cacheKey, ResourceListQuery.class, () -> {
|
||||
Resource resource = getResourceStoreDelegate().findByName(name, resourceServerId);
|
||||
|
||||
if (resource == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
if (query == null) {
|
||||
Long loaded = cache.getCurrentRevision(cacheKey);
|
||||
Resource model = getResourceStoreDelegate().findByName(name, resourceServerId);
|
||||
if (model == null) return null;
|
||||
if (invalidations.contains(model.getId())) return model;
|
||||
query = new ResourceListQuery(loaded, cacheKey, model.getId(), resourceServerId);
|
||||
cache.addRevisioned(query, startupRevision);
|
||||
return model;
|
||||
} else if (invalidations.contains(cacheKey)) {
|
||||
return getResourceStoreDelegate().findByName(name, resourceServerId);
|
||||
} else {
|
||||
String id = query.getResources().iterator().next();
|
||||
if (invalidations.contains(id)) {
|
||||
return getResourceStoreDelegate().findByName(name, resourceServerId);
|
||||
}
|
||||
return findById(id, query.getResourceServerId());
|
||||
|
||||
return Arrays.asList(resource);
|
||||
},
|
||||
(revision, resources) -> new ResourceListQuery(revision, cacheKey, resources.stream().map(resource -> resource.getId()).collect(Collectors.toSet()), resourceServerId), resourceServerId);
|
||||
|
||||
if (result.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return result.get(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Resource> findByOwner(String ownerId, String resourceServerId) {
|
||||
return getResourceStoreDelegate().findByOwner(ownerId, resourceServerId);
|
||||
String cacheKey = getResourceByOwnerCacheKey(ownerId, resourceServerId);
|
||||
return cacheQuery(cacheKey, ResourceListQuery.class, () -> getResourceStoreDelegate().findByOwner(ownerId, resourceServerId),
|
||||
(revision, resources) -> new ResourceListQuery(revision, cacheKey, resources.stream().map(resource -> resource.getId()).collect(Collectors.toSet()), resourceServerId), resourceServerId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Resource> findByUri(String uri, String resourceServerId) {
|
||||
return getResourceStoreDelegate().findByUri(uri, resourceServerId);
|
||||
if (uri == null) return null;
|
||||
String cacheKey = getResourceByUriCacheKey(uri, resourceServerId);
|
||||
return cacheQuery(cacheKey, ResourceListQuery.class, () -> getResourceStoreDelegate().findByUri(uri, resourceServerId),
|
||||
(revision, resources) -> new ResourceListQuery(revision, cacheKey, resources.stream().map(resource -> resource.getId()).collect(Collectors.toSet()), resourceServerId), resourceServerId);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -543,21 +582,52 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider {
|
|||
|
||||
@Override
|
||||
public List<Resource> findByScope(List<String> ids, String resourceServerId) {
|
||||
return getResourceStoreDelegate().findByScope(ids, resourceServerId);
|
||||
if (ids == null) return null;
|
||||
List<Resource> result = new ArrayList<>();
|
||||
|
||||
for (String id : ids) {
|
||||
String cacheKey = getResourceByScopeCacheKey(id, resourceServerId);
|
||||
result.addAll(cacheQuery(cacheKey, ResourceScopeListQuery.class, () -> getResourceStoreDelegate().findByScope(Arrays.asList(id), resourceServerId), (revision, resources) -> new ResourceScopeListQuery(revision, cacheKey, id, resources.stream().map(resource -> resource.getId()).collect(Collectors.toSet()), resourceServerId), resourceServerId));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Resource> findByType(String type, String resourceServerId) {
|
||||
return getResourceStoreDelegate().findByType(type, resourceServerId);
|
||||
if (type == null) return null;
|
||||
String cacheKey = getResourceByTypeCacheKey(type, resourceServerId);
|
||||
return cacheQuery(cacheKey, ResourceListQuery.class, () -> getResourceStoreDelegate().findByType(type, resourceServerId),
|
||||
(revision, resources) -> new ResourceListQuery(revision, cacheKey, resources.stream().map(resource -> resource.getId()).collect(Collectors.toSet()), resourceServerId), resourceServerId);
|
||||
}
|
||||
|
||||
private <R, Q extends ResourceQuery> List<R> cacheQuery(String cacheKey, Class<Q> queryType, Supplier<List<R>> resultSupplier, BiFunction<Long, List<R>, Q> querySupplier, String resourceServerId) {
|
||||
Q query = cache.get(cacheKey, queryType);
|
||||
if (query != null) {
|
||||
logger.tracev("cache hit for key: {0}", cacheKey);
|
||||
}
|
||||
if (query == null) {
|
||||
Long loaded = cache.getCurrentRevision(cacheKey);
|
||||
List<R> model = resultSupplier.get();
|
||||
if (model == null) return null;
|
||||
if (invalidations.contains(cacheKey)) return model;
|
||||
query = querySupplier.apply(loaded, model);
|
||||
cache.addRevisioned(query, startupRevision);
|
||||
return model;
|
||||
} else if (query.isInvalid(invalidations)) {
|
||||
return resultSupplier.get();
|
||||
} else {
|
||||
return query.getResources().stream().map(resourceId -> (R) findById(resourceId, resourceServerId)).collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected class PolicyCache implements PolicyStore {
|
||||
@Override
|
||||
public Policy create(AbstractPolicyRepresentation representation, ResourceServer resourceServer) {
|
||||
Policy resource = getPolicyStoreDelegate().create(representation, resourceServer);
|
||||
registerPolicyInvalidation(resource.getId(), resource.getName(), resourceServer.getId());
|
||||
return resource;
|
||||
Policy policy = getPolicyStoreDelegate().create(representation, resourceServer);
|
||||
registerPolicyInvalidation(policy.getId(), policy.getName(), policy.getResources().stream().map(resource1 -> resource1.getId()).collect(Collectors.toSet()), resourceServer.getId());
|
||||
return policy;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -567,8 +637,8 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider {
|
|||
if (policy == null) return;
|
||||
|
||||
cache.invalidateObject(id);
|
||||
invalidationEvents.add(PolicyRemovedEvent.create(id, policy.getName(), policy.getResourceServer().getId()));
|
||||
cache.policyRemoval(id, policy.getName(), policy.getResourceServer().getId(), invalidations);
|
||||
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);
|
||||
getPolicyStoreDelegate().delete(id);
|
||||
|
||||
}
|
||||
|
@ -604,27 +674,22 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider {
|
|||
public Policy findByName(String name, String resourceServerId) {
|
||||
if (name == null) return null;
|
||||
String cacheKey = getPolicyByNameCacheKey(name, resourceServerId);
|
||||
PolicyListQuery query = cache.get(cacheKey, PolicyListQuery.class);
|
||||
if (query != null) {
|
||||
logger.tracev("policy by name cache hit: {0}", name);
|
||||
List<Policy> result = cacheQuery(cacheKey, PolicyListQuery.class, () -> {
|
||||
Policy policy = getPolicyStoreDelegate().findByName(name, resourceServerId);
|
||||
|
||||
if (policy == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
if (query == null) {
|
||||
Long loaded = cache.getCurrentRevision(cacheKey);
|
||||
Policy model = getPolicyStoreDelegate().findByName(name, resourceServerId);
|
||||
if (model == null) return null;
|
||||
if (invalidations.contains(model.getId())) return model;
|
||||
query = new PolicyListQuery(loaded, cacheKey, model.getId(), resourceServerId);
|
||||
cache.addRevisioned(query, startupRevision);
|
||||
return model;
|
||||
} else if (invalidations.contains(cacheKey)) {
|
||||
return getPolicyStoreDelegate().findByName(name, resourceServerId);
|
||||
} else {
|
||||
String id = query.getPolicies().iterator().next();
|
||||
if (invalidations.contains(id)) {
|
||||
return getPolicyStoreDelegate().findByName(name, resourceServerId);
|
||||
}
|
||||
return findById(id, query.getResourceServerId());
|
||||
|
||||
return Arrays.asList(policy);
|
||||
},
|
||||
(revision, policies) -> new PolicyListQuery(revision, cacheKey, policies.stream().map(policy -> policy.getId()).collect(Collectors.toSet()), resourceServerId), resourceServerId);
|
||||
|
||||
if (result.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return result.get(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -639,17 +704,29 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider {
|
|||
|
||||
@Override
|
||||
public List<Policy> findByResource(String resourceId, String resourceServerId) {
|
||||
return getPolicyStoreDelegate().findByResource(resourceId, resourceServerId);
|
||||
String cacheKey = getPolicyByResource(resourceId, resourceServerId);
|
||||
return cacheQuery(cacheKey, PolicyResourceListQuery.class, () -> getPolicyStoreDelegate().findByResource(resourceId, resourceServerId),
|
||||
(revision, policies) -> new PolicyResourceListQuery(revision, cacheKey, resourceId, policies.stream().map(policy -> policy.getId()).collect(Collectors.toSet()), resourceServerId), resourceServerId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Policy> findByResourceType(String resourceType, String resourceServerId) {
|
||||
return getPolicyStoreDelegate().findByResourceType(resourceType, resourceServerId);
|
||||
String cacheKey = getPolicyByResourceType(resourceType, resourceServerId);
|
||||
return cacheQuery(cacheKey, PolicyResourceListQuery.class, () -> getPolicyStoreDelegate().findByResourceType(resourceType, resourceServerId),
|
||||
(revision, policies) -> new PolicyResourceListQuery(revision, cacheKey, resourceType, policies.stream().map(policy -> policy.getId()).collect(Collectors.toSet()), resourceServerId), resourceServerId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Policy> findByScopeIds(List<String> scopeIds, String resourceServerId) {
|
||||
return getPolicyStoreDelegate().findByScopeIds(scopeIds, resourceServerId);
|
||||
if (scopeIds == null) return null;
|
||||
List<Policy> result = new ArrayList<>();
|
||||
|
||||
for (String id : scopeIds) {
|
||||
String cacheKey = getPolicyByScope(id, resourceServerId);
|
||||
result.addAll(cacheQuery(cacheKey, PolicyScopeListQuery.class, () -> getPolicyStoreDelegate().findByScopeIds(Arrays.asList(id), resourceServerId), (revision, resources) -> new PolicyScopeListQuery(revision, cacheKey, id, resources.stream().map(resource -> resource.getId()).collect(Collectors.toSet()), resourceServerId), resourceServerId));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -661,6 +738,26 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider {
|
|||
public List<Policy> findDependentPolicies(String id, String resourceServerId) {
|
||||
return getPolicyStoreDelegate().findDependentPolicies(id, resourceServerId);
|
||||
}
|
||||
|
||||
private <R, Q extends PolicyQuery> List<R> cacheQuery(String cacheKey, Class<Q> queryType, Supplier<List<R>> resultSupplier, BiFunction<Long, List<R>, Q> querySupplier, String resourceServerId) {
|
||||
Q query = cache.get(cacheKey, queryType);
|
||||
if (query != null) {
|
||||
logger.tracev("cache hit for key: {0}", cacheKey);
|
||||
}
|
||||
if (query == null) {
|
||||
Long loaded = cache.getCurrentRevision(cacheKey);
|
||||
List<R> model = resultSupplier.get();
|
||||
if (model == null) return null;
|
||||
if (invalidations.contains(cacheKey)) return model;
|
||||
query = querySupplier.apply(loaded, model);
|
||||
cache.addRevisioned(query, startupRevision);
|
||||
return model;
|
||||
} else if (query.isInvalid(invalidations)) {
|
||||
return resultSupplier.get();
|
||||
} else {
|
||||
return query.getPolicies().stream().map(resourceId -> (R) findById(resourceId, resourceServerId)).collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* 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.cache.infinispan.authorization.entities;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public interface InResource {
|
||||
String getResourceId();
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.models.cache.infinispan.authorization.entities;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public interface InScope {
|
||||
String getScopeId();
|
||||
}
|
|
@ -9,7 +9,7 @@ import java.util.Set;
|
|||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class PolicyListQuery extends AbstractRevisioned implements InResourceServer {
|
||||
public class PolicyListQuery extends AbstractRevisioned implements PolicyQuery {
|
||||
private final Set<String> policies;
|
||||
private final String serverId;
|
||||
|
||||
|
@ -33,4 +33,9 @@ public class PolicyListQuery extends AbstractRevisioned implements InResourceSer
|
|||
public Set<String> getPolicies() {
|
||||
return policies;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInvalid(Set<String> invalidations) {
|
||||
return invalidations.contains(getId()) || invalidations.contains(getResourceServerId());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* 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.cache.infinispan.authorization.entities;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.keycloak.models.cache.infinispan.entities.Revisioned;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public interface PolicyQuery extends InResourceServer, Revisioned {
|
||||
|
||||
Set<String> getPolicies();
|
||||
boolean isInvalid(Set<String> invalidations);
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* 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.cache.infinispan.authorization.entities;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public class PolicyResourceListQuery extends PolicyListQuery implements InResource {
|
||||
|
||||
private final String resourceId;
|
||||
|
||||
public PolicyResourceListQuery(Long revision, String id, String resourceId, Set<String> policies, String serverId) {
|
||||
super(revision, id, policies, serverId);
|
||||
this.resourceId = resourceId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInvalid(Set<String> invalidations) {
|
||||
return super.isInvalid(invalidations) || invalidations.contains(getResourceId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getResourceId() {
|
||||
return resourceId;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* 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.cache.infinispan.authorization.entities;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public class PolicyScopeListQuery extends PolicyListQuery implements InScope {
|
||||
|
||||
private final String scopeId;
|
||||
|
||||
public PolicyScopeListQuery(Long revision, String id, String scopeId, Set<String> resources, String serverId) {
|
||||
super(revision, id, resources, serverId);
|
||||
this.scopeId = scopeId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getScopeId() {
|
||||
return scopeId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInvalid(Set<String> invalidations) {
|
||||
return super.isInvalid(invalidations) || invalidations.contains(getScopeId());
|
||||
}
|
||||
}
|
|
@ -9,7 +9,7 @@ import java.util.Set;
|
|||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class ResourceListQuery extends AbstractRevisioned implements InResourceServer {
|
||||
public class ResourceListQuery extends AbstractRevisioned implements ResourceQuery, InResourceServer {
|
||||
private final Set<String> resources;
|
||||
private final String serverId;
|
||||
|
||||
|
@ -33,4 +33,9 @@ public class ResourceListQuery extends AbstractRevisioned implements InResourceS
|
|||
public Set<String> getResources() {
|
||||
return resources;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInvalid(Set<String> invalidations) {
|
||||
return invalidations.contains(getId()) || invalidations.contains(getResourceServerId());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* 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.cache.infinispan.authorization.entities;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.keycloak.models.cache.infinispan.entities.Revisioned;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public interface ResourceQuery extends Revisioned {
|
||||
|
||||
Set<String> getResources();
|
||||
boolean isInvalid(Set<String> invalidations);
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
package org.keycloak.models.cache.infinispan.authorization.entities;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public class ResourceScopeListQuery extends ResourceListQuery implements InScope {
|
||||
|
||||
private final String scopeId;
|
||||
|
||||
public ResourceScopeListQuery(Long revision, String id, String scopeId, Set<String> resources, String serverId) {
|
||||
super(revision, id, resources, serverId);
|
||||
this.scopeId = scopeId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getScopeId() {
|
||||
return scopeId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInvalid(Set<String> invalidations) {
|
||||
return super.isInvalid(invalidations) || invalidations.contains(getScopeId());
|
||||
}
|
||||
}
|
|
@ -17,11 +17,11 @@
|
|||
|
||||
package org.keycloak.models.cache.infinispan.authorization.events;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.keycloak.models.cache.infinispan.authorization.StoreFactoryCacheManager;
|
||||
import org.keycloak.models.cache.infinispan.events.InvalidationEvent;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
|
@ -29,12 +29,14 @@ public class PolicyRemovedEvent extends InvalidationEvent implements Authorizati
|
|||
|
||||
private String id;
|
||||
private String name;
|
||||
private Set<String> resources;
|
||||
private String serverId;
|
||||
|
||||
public static PolicyRemovedEvent create(String id, String name, String serverId) {
|
||||
public static PolicyRemovedEvent create(String id, String name, Set<String> resources, String serverId) {
|
||||
PolicyRemovedEvent event = new PolicyRemovedEvent();
|
||||
event.id = id;
|
||||
event.name = name;
|
||||
event.resources = resources;
|
||||
event.serverId = serverId;
|
||||
return event;
|
||||
}
|
||||
|
@ -51,6 +53,6 @@ public class PolicyRemovedEvent extends InvalidationEvent implements Authorizati
|
|||
|
||||
@Override
|
||||
public void addInvalidations(StoreFactoryCacheManager cache, Set<String> invalidations) {
|
||||
cache.policyRemoval(id, name, serverId, invalidations);
|
||||
cache.policyRemoval(id, name, resources, serverId, invalidations);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,12 +29,14 @@ public class PolicyUpdatedEvent extends InvalidationEvent implements Authorizati
|
|||
|
||||
private String id;
|
||||
private String name;
|
||||
private static Set<String> resources;
|
||||
private String serverId;
|
||||
|
||||
public static PolicyUpdatedEvent create(String id, String name, String serverId) {
|
||||
public static PolicyUpdatedEvent create(String id, String name, Set<String> resources, String serverId) {
|
||||
PolicyUpdatedEvent event = new PolicyUpdatedEvent();
|
||||
event.id = id;
|
||||
event.name = name;
|
||||
event.resources = resources;
|
||||
event.serverId = serverId;
|
||||
return event;
|
||||
}
|
||||
|
@ -51,6 +53,6 @@ public class PolicyUpdatedEvent extends InvalidationEvent implements Authorizati
|
|||
|
||||
@Override
|
||||
public void addInvalidations(StoreFactoryCacheManager cache, Set<String> invalidations) {
|
||||
cache.policyUpdated(id, name, serverId, invalidations);
|
||||
cache.policyUpdated(id, name, resources, serverId, invalidations);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,12 +29,20 @@ public class ResourceRemovedEvent extends InvalidationEvent implements Authoriza
|
|||
|
||||
private String id;
|
||||
private String name;
|
||||
private String owner;
|
||||
private String serverId;
|
||||
private String type;
|
||||
private String uri;
|
||||
private Set<String> scopes;
|
||||
|
||||
public static ResourceRemovedEvent create(String id, String name, String serverId) {
|
||||
public static ResourceRemovedEvent create(String id, String name, String type, String uri, String owner, Set<String> scopes, String serverId) {
|
||||
ResourceRemovedEvent event = new ResourceRemovedEvent();
|
||||
event.id = id;
|
||||
event.name = name;
|
||||
event.type = type;
|
||||
event.uri = uri;
|
||||
event.owner = owner;
|
||||
event.scopes = scopes;
|
||||
event.serverId = serverId;
|
||||
return event;
|
||||
}
|
||||
|
@ -51,6 +59,6 @@ public class ResourceRemovedEvent extends InvalidationEvent implements Authoriza
|
|||
|
||||
@Override
|
||||
public void addInvalidations(StoreFactoryCacheManager cache, Set<String> invalidations) {
|
||||
cache.resourceRemoval(id, name, serverId, invalidations);
|
||||
cache.resourceRemoval(id, name, type, uri, owner, scopes, serverId, invalidations);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,11 +30,17 @@ public class ResourceUpdatedEvent extends InvalidationEvent implements Authoriza
|
|||
private String id;
|
||||
private String name;
|
||||
private String serverId;
|
||||
private String type;
|
||||
private String uri;
|
||||
private Set<String> scopes;
|
||||
|
||||
public static ResourceUpdatedEvent create(String id, String name, String serverId) {
|
||||
public static ResourceUpdatedEvent create(String id, String name, String type, String uri, Set<String> scopes, String serverId) {
|
||||
ResourceUpdatedEvent event = new ResourceUpdatedEvent();
|
||||
event.id = id;
|
||||
event.name = name;
|
||||
event.type = type;
|
||||
event.uri = uri;
|
||||
event.scopes = scopes;
|
||||
event.serverId = serverId;
|
||||
return event;
|
||||
}
|
||||
|
@ -51,6 +57,6 @@ public class ResourceUpdatedEvent extends InvalidationEvent implements Authoriza
|
|||
|
||||
@Override
|
||||
public void addInvalidations(StoreFactoryCacheManager cache, Set<String> invalidations) {
|
||||
cache.resourceUpdated(id, name, serverId, invalidations);
|
||||
cache.resourceUpdated(id, name, type, uri, scopes, serverId, invalidations);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* 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.cache.infinispan.authorization.stream;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Map;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.keycloak.models.cache.infinispan.authorization.entities.InResource;
|
||||
import org.keycloak.models.cache.infinispan.entities.Revisioned;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public class InResourcePredicate implements Predicate<Map.Entry<String, Revisioned>>, Serializable {
|
||||
|
||||
private String resourceId;
|
||||
|
||||
public static InResourcePredicate create() {
|
||||
return new InResourcePredicate();
|
||||
}
|
||||
|
||||
public InResourcePredicate resource(String id) {
|
||||
resourceId = id;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean test(Map.Entry<String, Revisioned> entry) {
|
||||
Object value = entry.getValue();
|
||||
if (value == null) return false;
|
||||
if (!(value instanceof InResource)) return false;
|
||||
|
||||
return resourceId.equals(((InResource)value).getResourceId());
|
||||
}
|
||||
}
|
35
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/stream/InScopePredicate.java
vendored
Executable file
35
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/stream/InScopePredicate.java
vendored
Executable file
|
@ -0,0 +1,35 @@
|
|||
package org.keycloak.models.cache.infinispan.authorization.stream;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Map;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.keycloak.models.cache.infinispan.authorization.entities.InResourceServer;
|
||||
import org.keycloak.models.cache.infinispan.authorization.entities.InScope;
|
||||
import org.keycloak.models.cache.infinispan.entities.Revisioned;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class InScopePredicate implements Predicate<Map.Entry<String, Revisioned>>, Serializable {
|
||||
private String scopeId;
|
||||
|
||||
public static InScopePredicate create() {
|
||||
return new InScopePredicate();
|
||||
}
|
||||
|
||||
public InScopePredicate scope(String id) {
|
||||
scopeId = id;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean test(Map.Entry<String, Revisioned> entry) {
|
||||
Object value = entry.getValue();
|
||||
if (value == null) return false;
|
||||
if (!(value instanceof InScope)) return false;
|
||||
|
||||
return scopeId.equals(((InScope)value).getScopeId());
|
||||
}
|
||||
}
|
|
@ -29,18 +29,20 @@ import org.keycloak.models.jpa.entities.ClientEntity;
|
|||
import org.keycloak.models.jpa.entities.ClientTemplateEntity;
|
||||
import org.keycloak.models.jpa.entities.ProtocolMapperEntity;
|
||||
import org.keycloak.models.jpa.entities.RoleEntity;
|
||||
import org.keycloak.models.jpa.entities.ScopeMappingEntity;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.TypedQuery;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
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.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
|
@ -240,47 +242,22 @@ public class ClientAdapter implements ClientModel, JpaModel<ClientEntity> {
|
|||
|
||||
@Override
|
||||
public Set<RoleModel> getScopeMappings() {
|
||||
TypedQuery<String> query = em.createNamedQuery("clientScopeMappingIds", String.class);
|
||||
query.setParameter("client", getEntity());
|
||||
List<String> ids = query.getResultList();
|
||||
Set<RoleModel> roles = new HashSet<RoleModel>();
|
||||
for (String roleId : ids) {
|
||||
RoleModel role = realm.getRoleById(roleId);
|
||||
if (role == null) continue;
|
||||
roles.add(role);
|
||||
}
|
||||
return roles;
|
||||
return getEntity().getScopeMapping().stream()
|
||||
.map(RoleEntity::getId)
|
||||
.map(realm::getRoleById)
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addScopeMapping(RoleModel role) {
|
||||
Set<RoleModel> roles = getScopeMappings();
|
||||
if (roles.contains(role)) return;
|
||||
ScopeMappingEntity entity = new ScopeMappingEntity();
|
||||
entity.setClient(getEntity());
|
||||
RoleEntity roleEntity = RoleAdapter.toRoleEntity(role, em);
|
||||
entity.setRole(roleEntity);
|
||||
em.persist(entity);
|
||||
em.flush();
|
||||
em.detach(entity);
|
||||
getEntity().getScopeMapping().add(roleEntity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteScopeMapping(RoleModel role) {
|
||||
TypedQuery<ScopeMappingEntity> query = getRealmScopeMappingQuery(role);
|
||||
List<ScopeMappingEntity> results = query.getResultList();
|
||||
if (results.size() == 0) return;
|
||||
for (ScopeMappingEntity entity : results) {
|
||||
em.remove(entity);
|
||||
}
|
||||
}
|
||||
|
||||
protected TypedQuery<ScopeMappingEntity> getRealmScopeMappingQuery(RoleModel role) {
|
||||
TypedQuery<ScopeMappingEntity> query = em.createNamedQuery("hasScope", ScopeMappingEntity.class);
|
||||
query.setParameter("client", getEntity());
|
||||
RoleEntity roleEntity = RoleAdapter.toRoleEntity(role, em);
|
||||
query.setParameter("role", roleEntity);
|
||||
return query;
|
||||
getEntity().getScopeMapping().remove(RoleAdapter.toRoleEntity(role, em));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -689,7 +666,6 @@ public class ClientAdapter implements ClientModel, JpaModel<ClientEntity> {
|
|||
}
|
||||
RoleEntity roleEntity = RoleAdapter.toRoleEntity(role, em);
|
||||
entities.add(roleEntity);
|
||||
em.flush();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -43,6 +43,7 @@ import java.util.HashSet;
|
|||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
|
@ -133,14 +134,6 @@ public class JpaRealmProvider implements RealmProvider {
|
|||
|
||||
int num = em.createNamedQuery("deleteGroupRoleMappingsByRealm")
|
||||
.setParameter("realm", realm).executeUpdate();
|
||||
num = em.createNamedQuery("deleteGroupAttributesByRealm")
|
||||
.setParameter("realm", realm).executeUpdate();
|
||||
num = em.createNamedQuery("deleteGroupsByRealm")
|
||||
.setParameter("realm", realm).executeUpdate();
|
||||
num = em.createNamedQuery("deleteComponentConfigByRealm")
|
||||
.setParameter("realm", realm).executeUpdate();
|
||||
num = em.createNamedQuery("deleteComponentByRealm")
|
||||
.setParameter("realm", realm).executeUpdate();
|
||||
|
||||
TypedQuery<String> query = em.createNamedQuery("getClientIdsByRealm", String.class);
|
||||
query.setParameter("realm", realm.getId());
|
||||
|
@ -228,7 +221,6 @@ public class JpaRealmProvider implements RealmProvider {
|
|||
roleEntity.setClientRole(true);
|
||||
roleEntity.setRealmId(realm.getId());
|
||||
em.persist(roleEntity);
|
||||
em.flush();
|
||||
RoleAdapter adapter = new RoleAdapter(session, realm, em, roleEntity);
|
||||
return adapter;
|
||||
}
|
||||
|
@ -281,10 +273,11 @@ public class JpaRealmProvider implements RealmProvider {
|
|||
RoleEntity roleEntity = em.getReference(RoleEntity.class, role.getId());
|
||||
String compositeRoleTable = JpaUtils.getTableNameForNativeQuery("COMPOSITE_ROLE", em);
|
||||
em.createNativeQuery("delete from " + compositeRoleTable + " where CHILD_ROLE = :role").setParameter("role", roleEntity).executeUpdate();
|
||||
em.createNamedQuery("deleteScopeMappingByRole").setParameter("role", roleEntity).executeUpdate();
|
||||
realm.getClients().forEach(c -> c.deleteScopeMapping(role));
|
||||
em.createNamedQuery("deleteTemplateScopeMappingByRole").setParameter("role", roleEntity).executeUpdate();
|
||||
int val = em.createNamedQuery("deleteGroupRoleMappingsByRole").setParameter("roleId", roleEntity.getId()).executeUpdate();
|
||||
|
||||
em.flush();
|
||||
em.remove(roleEntity);
|
||||
|
||||
session.getKeycloakSessionFactory().publish(new RoleContainerModel.RoleRemovedEvent() {
|
||||
|
@ -337,27 +330,23 @@ public class JpaRealmProvider implements RealmProvider {
|
|||
|
||||
@Override
|
||||
public List<GroupModel> getGroups(RealmModel realm) {
|
||||
List<String> groups = em.createNamedQuery("getAllGroupIdsByRealm", String.class)
|
||||
.setParameter("realm", realm.getId()).getResultList();
|
||||
if (groups == null) return Collections.EMPTY_LIST;
|
||||
List<GroupModel> list = new LinkedList<>();
|
||||
for (String id : groups) {
|
||||
list.add(session.realms().getGroupById(id, realm));
|
||||
}
|
||||
return Collections.unmodifiableList(list);
|
||||
RealmEntity ref = em.getReference(RealmEntity.class, realm.getId());
|
||||
|
||||
return ref.getGroups().stream()
|
||||
.map(g -> session.realms().getGroupById(g.getId(), realm))
|
||||
.collect(Collectors.collectingAndThen(
|
||||
Collectors.toList(), Collections::unmodifiableList));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<GroupModel> getTopLevelGroups(RealmModel realm) {
|
||||
List<String> groups = em.createNamedQuery("getTopLevelGroupIds", String.class)
|
||||
.setParameter("realm", realm.getId())
|
||||
.getResultList();
|
||||
if (groups == null) return Collections.EMPTY_LIST;
|
||||
List<GroupModel> list = new LinkedList<>();
|
||||
for (String id : groups) {
|
||||
list.add(session.realms().getGroupById(id, realm));
|
||||
}
|
||||
return Collections.unmodifiableList(list);
|
||||
RealmEntity ref = em.getReference(RealmEntity.class, realm.getId());
|
||||
|
||||
return ref.getGroups().stream()
|
||||
.filter(g -> g.getParent() == null)
|
||||
.map(g -> session.realms().getGroupById(g.getId(), realm))
|
||||
.collect(Collectors.collectingAndThen(
|
||||
Collectors.toList(), Collections::unmodifiableList));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -377,9 +366,11 @@ public class JpaRealmProvider implements RealmProvider {
|
|||
if (!groupEntity.getRealm().getId().equals(realm.getId())) {
|
||||
return false;
|
||||
}
|
||||
// I don't think we need this as GroupEntity has cascade removal. It causes batch errors if you turn this on.
|
||||
// em.createNamedQuery("deleteGroupAttributesByGroup").setParameter("group", groupEntity).executeUpdate();
|
||||
em.createNamedQuery("deleteGroupRoleMappingsByGroup").setParameter("group", groupEntity).executeUpdate();
|
||||
|
||||
RealmEntity realmEntity = em.getReference(RealmEntity.class, realm.getId());
|
||||
realmEntity.getGroups().remove(groupEntity);
|
||||
|
||||
em.remove(groupEntity);
|
||||
return true;
|
||||
|
||||
|
@ -401,6 +392,7 @@ public class JpaRealmProvider implements RealmProvider {
|
|||
RealmEntity realmEntity = em.getReference(RealmEntity.class, realm.getId());
|
||||
groupEntity.setRealm(realmEntity);
|
||||
em.persist(groupEntity);
|
||||
realmEntity.getGroups().add(groupEntity);
|
||||
|
||||
GroupAdapter adapter = new GroupAdapter(realm, em, groupEntity);
|
||||
return adapter;
|
||||
|
@ -494,9 +486,6 @@ public class JpaRealmProvider implements RealmProvider {
|
|||
|
||||
ClientEntity clientEntity = ((ClientAdapter)client).getEntity();
|
||||
|
||||
em.createNamedQuery("deleteScopeMappingByClient").setParameter("client", clientEntity).executeUpdate();
|
||||
em.flush();
|
||||
|
||||
session.getKeycloakSessionFactory().publish(new RealmModel.ClientRemovedEvent() {
|
||||
@Override
|
||||
public ClientModel getClient() {
|
||||
|
|
|
@ -58,7 +58,6 @@ import org.keycloak.models.utils.ComponentUtil;
|
|||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.TypedQuery;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
@ -68,7 +67,10 @@ import java.util.Iterator;
|
|||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
|
@ -1375,16 +1377,10 @@ public class RealmAdapter implements RealmModel, JpaModel<RealmEntity> {
|
|||
|
||||
@Override
|
||||
public List<AuthenticationFlowModel> getAuthenticationFlows() {
|
||||
TypedQuery<AuthenticationFlowEntity> query = em.createNamedQuery("getAuthenticationFlowsByRealm", AuthenticationFlowEntity.class);
|
||||
query.setParameter("realm", realm);
|
||||
List<AuthenticationFlowEntity> flows = query.getResultList();
|
||||
if (flows.isEmpty()) return Collections.EMPTY_LIST;
|
||||
List<AuthenticationFlowModel> models = new LinkedList<>();
|
||||
for (AuthenticationFlowEntity entity : flows) {
|
||||
AuthenticationFlowModel model = entityToModel(entity);
|
||||
models.add(model);
|
||||
}
|
||||
return Collections.unmodifiableList(models);
|
||||
return realm.getAuthenticationFlows().stream()
|
||||
.map(this::entityToModel)
|
||||
.collect(Collectors.collectingAndThen(
|
||||
Collectors.toList(), Collections::unmodifiableList));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1461,26 +1457,20 @@ public class RealmAdapter implements RealmModel, JpaModel<RealmEntity> {
|
|||
entity.setRealm(realm);
|
||||
realm.getAuthenticationFlows().add(entity);
|
||||
em.persist(entity);
|
||||
em.flush();
|
||||
model.setId(entity.getId());
|
||||
return model;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<AuthenticationExecutionModel> getAuthenticationExecutions(String flowId) {
|
||||
TypedQuery<AuthenticationExecutionEntity> query = em.createNamedQuery("getAuthenticationExecutionsByFlow", AuthenticationExecutionEntity.class);
|
||||
AuthenticationFlowEntity flow = em.getReference(AuthenticationFlowEntity.class, flowId);
|
||||
query.setParameter("realm", realm);
|
||||
query.setParameter("parentFlow", flow);
|
||||
List<AuthenticationExecutionEntity> queryResult = query.getResultList();
|
||||
if (queryResult.isEmpty()) return Collections.EMPTY_LIST;
|
||||
List<AuthenticationExecutionModel> executions = new LinkedList<>();
|
||||
for (AuthenticationExecutionEntity entity : queryResult) {
|
||||
AuthenticationExecutionModel model = entityToModel(entity);
|
||||
executions.add(model);
|
||||
}
|
||||
Collections.sort(executions, AuthenticationExecutionModel.ExecutionComparator.SINGLETON);
|
||||
return Collections.unmodifiableList(executions);
|
||||
|
||||
return flow.getExecutions().stream()
|
||||
.filter(e -> getId().equals(e.getRealm().getId()))
|
||||
.map(this::entityToModel)
|
||||
.sorted(AuthenticationExecutionModel.ExecutionComparator.SINGLETON)
|
||||
.collect(Collectors.collectingAndThen(
|
||||
Collectors.toList(), Collections::unmodifiableList));
|
||||
}
|
||||
|
||||
public AuthenticationExecutionModel entityToModel(AuthenticationExecutionEntity entity) {
|
||||
|
@ -1519,7 +1509,6 @@ public class RealmAdapter implements RealmModel, JpaModel<RealmEntity> {
|
|||
entity.setRealm(realm);
|
||||
entity.setAutheticatorFlow(model.isAuthenticatorFlow());
|
||||
em.persist(entity);
|
||||
em.flush();
|
||||
model.setId(entity.getId());
|
||||
return model;
|
||||
|
||||
|
@ -1557,7 +1546,6 @@ public class RealmAdapter implements RealmModel, JpaModel<RealmEntity> {
|
|||
auth.setConfig(model.getConfig());
|
||||
realm.getAuthenticatorConfigs().add(auth);
|
||||
em.persist(auth);
|
||||
em.flush();
|
||||
model.setId(auth.getId());
|
||||
return model;
|
||||
}
|
||||
|
@ -1850,12 +1838,14 @@ public class RealmAdapter implements RealmModel, JpaModel<RealmEntity> {
|
|||
c.setSubType(model.getSubType());
|
||||
c.setRealm(realm);
|
||||
em.persist(c);
|
||||
realm.getComponents().add(c);
|
||||
setConfig(model, c);
|
||||
model.setId(c.getId());
|
||||
return model;
|
||||
}
|
||||
|
||||
protected void setConfig(ComponentModel model, ComponentEntity c) {
|
||||
c.getComponentConfigs().clear();
|
||||
for (String key : model.getConfig().keySet()) {
|
||||
List<String> vals = model.getConfig().get(key);
|
||||
if (vals == null) {
|
||||
|
@ -1867,7 +1857,7 @@ public class RealmAdapter implements RealmModel, JpaModel<RealmEntity> {
|
|||
config.setName(key);
|
||||
config.setValue(val);
|
||||
config.setComponent(c);
|
||||
em.persist(config);
|
||||
c.getComponentConfigs().add(config);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1884,8 +1874,6 @@ public class RealmAdapter implements RealmModel, JpaModel<RealmEntity> {
|
|||
c.setProviderType(component.getProviderType());
|
||||
c.setParentId(component.getParentId());
|
||||
c.setSubType(component.getSubType());
|
||||
em.createNamedQuery("deleteComponentConfigByComponent").setParameter("component", c).executeUpdate();
|
||||
em.flush();
|
||||
setConfig(component, c);
|
||||
ComponentUtil.notifyUpdated(session, this, old, component);
|
||||
|
||||
|
@ -1898,55 +1886,39 @@ public class RealmAdapter implements RealmModel, JpaModel<RealmEntity> {
|
|||
if (c == null) return;
|
||||
session.users().preRemove(this, component);
|
||||
removeComponents(component.getId());
|
||||
em.createNamedQuery("deleteComponentConfigByComponent").setParameter("component", c).executeUpdate();
|
||||
em.remove(c);
|
||||
getEntity().getComponents().remove(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeComponents(String parentId) {
|
||||
TypedQuery<String> query = em.createNamedQuery("getComponentIdsByParent", String.class)
|
||||
.setParameter("realm", realm)
|
||||
.setParameter("parentId", parentId);
|
||||
List<String> results = query.getResultList();
|
||||
if (results.isEmpty()) return;
|
||||
for (String id : results) {
|
||||
session.users().preRemove(this, getComponent(id));
|
||||
}
|
||||
em.createNamedQuery("deleteComponentConfigByParent").setParameter("parentId", parentId).executeUpdate();
|
||||
em.createNamedQuery("deleteComponentByParent").setParameter("parentId", parentId).executeUpdate();
|
||||
Predicate<ComponentEntity> sameParent = c -> Objects.equals(parentId, c.getParentId());
|
||||
|
||||
getEntity().getComponents().stream()
|
||||
.filter(sameParent)
|
||||
.map(this::entityToModel)
|
||||
.forEach(c -> session.users().preRemove(this, c));
|
||||
|
||||
getEntity().getComponents().removeIf(sameParent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ComponentModel> getComponents(String parentId, String providerType) {
|
||||
public List<ComponentModel> getComponents(String parentId, final String providerType) {
|
||||
if (parentId == null) parentId = getId();
|
||||
TypedQuery<ComponentEntity> query = em.createNamedQuery("getComponentsByParentAndType", ComponentEntity.class)
|
||||
.setParameter("realm", realm)
|
||||
.setParameter("parentId", parentId)
|
||||
.setParameter("providerType", providerType);
|
||||
List<ComponentEntity> results = query.getResultList();
|
||||
List<ComponentModel> rtn = new LinkedList<>();
|
||||
for (ComponentEntity c : results) {
|
||||
ComponentModel model = entityToModel(c);
|
||||
rtn.add(model);
|
||||
final String parent = parentId;
|
||||
|
||||
}
|
||||
return rtn;
|
||||
return realm.getComponents().stream()
|
||||
.filter(c -> parent.equals(c.getParentId())
|
||||
&& providerType.equals(c.getProviderType()))
|
||||
.map(this::entityToModel)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ComponentModel> getComponents(String parentId) {
|
||||
TypedQuery<ComponentEntity> query = em.createNamedQuery("getComponentsByParent", ComponentEntity.class)
|
||||
.setParameter("realm", realm)
|
||||
.setParameter("parentId", parentId);
|
||||
List<ComponentEntity> results = query.getResultList();
|
||||
List<ComponentModel> rtn = new LinkedList<>();
|
||||
for (ComponentEntity c : results) {
|
||||
ComponentModel model = entityToModel(c);
|
||||
rtn.add(model);
|
||||
|
||||
}
|
||||
return rtn;
|
||||
public List<ComponentModel> getComponents(final String parentId) {
|
||||
return realm.getComponents().stream()
|
||||
.filter(c -> parentId.equals(c.getParentId()))
|
||||
.map(this::entityToModel)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
protected ComponentModel entityToModel(ComponentEntity c) {
|
||||
|
@ -1958,10 +1930,7 @@ public class RealmAdapter implements RealmModel, JpaModel<RealmEntity> {
|
|||
model.setSubType(c.getSubType());
|
||||
model.setParentId(c.getParentId());
|
||||
MultivaluedHashMap<String, String> config = new MultivaluedHashMap<>();
|
||||
TypedQuery<ComponentConfigEntity> configQuery = em.createNamedQuery("getComponentConfig", ComponentConfigEntity.class)
|
||||
.setParameter("component", c);
|
||||
List<ComponentConfigEntity> configResults = configQuery.getResultList();
|
||||
for (ComponentConfigEntity configEntity : configResults) {
|
||||
for (ComponentConfigEntity configEntity : c.getComponentConfigs()) {
|
||||
config.add(configEntity.getName(), configEntity.getValue());
|
||||
}
|
||||
model.setConfig(config);
|
||||
|
@ -1970,16 +1939,7 @@ public class RealmAdapter implements RealmModel, JpaModel<RealmEntity> {
|
|||
|
||||
@Override
|
||||
public List<ComponentModel> getComponents() {
|
||||
TypedQuery<ComponentEntity> query = em.createNamedQuery("getComponents", ComponentEntity.class)
|
||||
.setParameter("realm", realm);
|
||||
List<ComponentEntity> results = query.getResultList();
|
||||
List<ComponentModel> rtn = new LinkedList<>();
|
||||
for (ComponentEntity c : results) {
|
||||
ComponentModel model = entityToModel(c);
|
||||
rtn.add(model);
|
||||
|
||||
}
|
||||
return rtn;
|
||||
return realm.getComponents().stream().map(this::entityToModel).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -101,7 +101,6 @@ public class RoleAdapter implements RoleModel, JpaModel<RoleEntity> {
|
|||
if (composite.equals(entity)) return;
|
||||
}
|
||||
getEntity().getCompositeRoles().add(entity);
|
||||
em.flush();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -37,11 +37,6 @@ import javax.persistence.Table;
|
|||
*/
|
||||
@Table(name="AUTHENTICATION_EXECUTION")
|
||||
@Entity
|
||||
@NamedQueries({
|
||||
@NamedQuery(name="getAuthenticationExecutionsByFlow", query="select authenticator from AuthenticationExecutionEntity authenticator where authenticator.realm = :realm and authenticator.parentFlow = :parentFlow"),
|
||||
@NamedQuery(name="deleteAuthenticationExecutionsByRealm", query="delete from AuthenticationExecutionEntity authenticator where authenticator.realm = :realm"),
|
||||
@NamedQuery(name="deleteAuthenticationExecutionsByRealmAndFlow", query="delete from AuthenticationExecutionEntity authenticator where authenticator.realm = :realm and authenticator.parentFlow = :parentFlow"),
|
||||
})
|
||||
public class AuthenticationExecutionEntity {
|
||||
@Id
|
||||
@Column(name="ID", length = 36)
|
||||
|
|
|
@ -39,10 +39,6 @@ import java.util.Collection;
|
|||
*/
|
||||
@Table(name="AUTHENTICATION_FLOW")
|
||||
@Entity
|
||||
@NamedQueries({
|
||||
@NamedQuery(name="getAuthenticationFlowsByRealm", query="select flow from AuthenticationFlowEntity flow where flow.realm = :realm"),
|
||||
@NamedQuery(name="deleteAuthenticationFlowByRealm", query="delete from AuthenticationFlowEntity flow where flow.realm = :realm")
|
||||
})
|
||||
public class AuthenticationFlowEntity {
|
||||
@Id
|
||||
@Column(name="ID", length = 36)
|
||||
|
|
|
@ -162,6 +162,10 @@ public class ClientEntity {
|
|||
@JoinTable(name="CLIENT_DEFAULT_ROLES", joinColumns = { @JoinColumn(name="CLIENT_ID")}, inverseJoinColumns = { @JoinColumn(name="ROLE_ID")})
|
||||
Collection<RoleEntity> defaultRoles = new ArrayList<RoleEntity>();
|
||||
|
||||
@OneToMany(fetch = FetchType.LAZY)
|
||||
@JoinTable(name="SCOPE_MAPPING", joinColumns = { @JoinColumn(name="CLIENT_ID")}, inverseJoinColumns = { @JoinColumn(name="ROLE_ID")})
|
||||
protected Set<RoleEntity> scopeMapping = new HashSet<>();
|
||||
|
||||
@ElementCollection
|
||||
@MapKeyColumn(name="NAME")
|
||||
@Column(name="VALUE")
|
||||
|
@ -456,6 +460,14 @@ public class ClientEntity {
|
|||
this.useTemplateMappers = useTemplateMappers;
|
||||
}
|
||||
|
||||
public Set<RoleEntity> getScopeMapping() {
|
||||
return scopeMapping;
|
||||
}
|
||||
|
||||
public void setScopeMapping(Set<RoleEntity> scopeMapping) {
|
||||
this.scopeMapping = scopeMapping;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
|
|
|
@ -33,12 +33,6 @@ import javax.persistence.Table;
|
|||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
@NamedQueries({
|
||||
@NamedQuery(name="getComponentConfig", query="select attr from ComponentConfigEntity attr where attr.component = :component"),
|
||||
@NamedQuery(name="deleteComponentConfigByComponent", query="delete from ComponentConfigEntity attr where attr.component = :component"),
|
||||
@NamedQuery(name="deleteComponentConfigByRealm", query="delete from ComponentConfigEntity attr where attr.component IN (select u from ComponentEntity u where u.realm=:realm)"),
|
||||
@NamedQuery(name="deleteComponentConfigByParent", query="delete from ComponentConfigEntity attr where attr.component IN (select u from ComponentEntity u where u.parentId=:parentId)"),
|
||||
})
|
||||
@Table(name="COMPONENT_CONFIG")
|
||||
@Entity
|
||||
public class ComponentConfigEntity {
|
||||
|
|
|
@ -17,8 +17,12 @@
|
|||
|
||||
package org.keycloak.models.jpa.entities;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.persistence.Access;
|
||||
import javax.persistence.AccessType;
|
||||
import javax.persistence.CascadeType;
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.FetchType;
|
||||
|
@ -27,19 +31,12 @@ import javax.persistence.JoinColumn;
|
|||
import javax.persistence.ManyToOne;
|
||||
import javax.persistence.NamedQueries;
|
||||
import javax.persistence.NamedQuery;
|
||||
import javax.persistence.OneToMany;
|
||||
import javax.persistence.Table;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bburke@redhat.com">Bill Burke</a>
|
||||
*/
|
||||
@NamedQueries({
|
||||
@NamedQuery(name="getComponents", query="select attr from ComponentEntity attr where attr.realm = :realm"),
|
||||
@NamedQuery(name="getComponentsByParentAndType", query="select attr from ComponentEntity attr where attr.realm = :realm and attr.providerType = :providerType and attr.parentId = :parentId"),
|
||||
@NamedQuery(name="getComponentByParent", query="select attr from ComponentEntity attr where attr.realm = :realm and attr.parentId = :parentId"),
|
||||
@NamedQuery(name="getComponentIdsByParent", query="select attr.id from ComponentEntity attr where attr.realm = :realm and attr.parentId = :parentId"),
|
||||
@NamedQuery(name="deleteComponentByRealm", query="delete from ComponentEntity c where c.realm = :realm"),
|
||||
@NamedQuery(name="deleteComponentByParent", query="delete from ComponentEntity c where c.parentId = :parentId")
|
||||
})
|
||||
@Entity
|
||||
@Table(name="COMPONENT")
|
||||
public class ComponentEntity {
|
||||
|
@ -68,6 +65,9 @@ public class ComponentEntity {
|
|||
@Column(name="SUB_TYPE")
|
||||
protected String subType;
|
||||
|
||||
@OneToMany(fetch = FetchType.LAZY, cascade ={ CascadeType.ALL}, orphanRemoval = true, mappedBy = "component")
|
||||
Set<ComponentConfigEntity> componentConfigs = new HashSet<>();
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
@ -124,6 +124,14 @@ public class ComponentEntity {
|
|||
this.realm = realm;
|
||||
}
|
||||
|
||||
public Set<ComponentConfigEntity> getComponentConfigs() {
|
||||
return componentConfigs;
|
||||
}
|
||||
|
||||
public void setComponentConfigs(Set<ComponentConfigEntity> componentConfigs) {
|
||||
this.componentConfigs = componentConfigs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
|
|
|
@ -35,8 +35,6 @@ import javax.persistence.Table;
|
|||
*/
|
||||
@NamedQueries({
|
||||
@NamedQuery(name="getGroupAttributesByNameAndValue", query="select attr from GroupAttributeEntity attr where attr.name = :name and attr.value = :value"),
|
||||
@NamedQuery(name="deleteGroupAttributesByGroup", query="delete from GroupAttributeEntity attr where attr.group = :group"),
|
||||
@NamedQuery(name="deleteGroupAttributesByRealm", query="delete from GroupAttributeEntity attr where attr.group IN (select u from GroupEntity u where u.realm=:realm)")
|
||||
})
|
||||
@Table(name="GROUP_ATTRIBUTE")
|
||||
@Entity
|
||||
|
|
|
@ -38,13 +38,7 @@ import java.util.Collection;
|
|||
* @version $Revision: 1 $
|
||||
*/
|
||||
@NamedQueries({
|
||||
@NamedQuery(name="getAllGroupsByRealm", query="select u from GroupEntity u where u.realm = :realm order by u.name"),
|
||||
@NamedQuery(name="getAllGroupIdsByRealm", query="select u.id from GroupEntity u where u.realm.id = :realm order by u.name"),
|
||||
@NamedQuery(name="getGroupById", query="select u from GroupEntity u where u.id = :id and u.realm = :realm"),
|
||||
@NamedQuery(name="getGroupIdsByParent", query="select u.id from GroupEntity u where u.parent = :parent"),
|
||||
@NamedQuery(name="getTopLevelGroupIds", query="select u.id from GroupEntity u where u.parent is null and u.realm.id = :realm"),
|
||||
@NamedQuery(name="getGroupCount", query="select count(u) from GroupEntity u where u.realm = :realm"),
|
||||
@NamedQuery(name="deleteGroupsByRealm", query="delete from GroupEntity u where u.realm = :realm")
|
||||
})
|
||||
@Entity
|
||||
@Table(name="KEYCLOAK_GROUP")
|
||||
|
|
|
@ -159,6 +159,9 @@ public class RealmEntity {
|
|||
@JoinTable(name="REALM_DEFAULT_GROUPS", joinColumns = { @JoinColumn(name="REALM_ID")}, inverseJoinColumns = { @JoinColumn(name="GROUP_ID")})
|
||||
protected Collection<GroupEntity> defaultGroups = new ArrayList<>();
|
||||
|
||||
@OneToMany(fetch = FetchType.LAZY, cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm")
|
||||
protected Collection<GroupEntity> groups = new ArrayList<>();
|
||||
|
||||
@Column(name="EVENTS_ENABLED")
|
||||
protected boolean eventsEnabled;
|
||||
@Column(name="EVENTS_EXPIRATION")
|
||||
|
@ -199,6 +202,9 @@ public class RealmEntity {
|
|||
@OneToMany(cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm")
|
||||
Collection<AuthenticationFlowEntity> authenticationFlows = new ArrayList<>();
|
||||
|
||||
@OneToMany(fetch = FetchType.LAZY, cascade ={CascadeType.ALL}, orphanRemoval = true, mappedBy = "realm")
|
||||
Set<ComponentEntity> components = new HashSet<>();
|
||||
|
||||
@Column(name="BROWSER_FLOW")
|
||||
protected String browserFlow;
|
||||
|
||||
|
@ -426,6 +432,14 @@ public class RealmEntity {
|
|||
this.defaultGroups = defaultGroups;
|
||||
}
|
||||
|
||||
public Collection<GroupEntity> getGroups() {
|
||||
return groups;
|
||||
}
|
||||
|
||||
public void setGroups(Collection<GroupEntity> groups) {
|
||||
this.groups = groups;
|
||||
}
|
||||
|
||||
public String getPasswordPolicy() {
|
||||
return passwordPolicy;
|
||||
}
|
||||
|
@ -623,6 +637,14 @@ public class RealmEntity {
|
|||
this.authenticationFlows = authenticationFlows;
|
||||
}
|
||||
|
||||
public Set<ComponentEntity> getComponents() {
|
||||
return components;
|
||||
}
|
||||
|
||||
public void setComponents(Set<ComponentEntity> components) {
|
||||
this.components = components;
|
||||
}
|
||||
|
||||
public String getOtpPolicyType() {
|
||||
return otpPolicyType;
|
||||
}
|
||||
|
|
|
@ -1,138 +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.jpa.entities;
|
||||
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.FetchType;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.IdClass;
|
||||
import javax.persistence.JoinColumn;
|
||||
import javax.persistence.ManyToOne;
|
||||
import javax.persistence.NamedQueries;
|
||||
import javax.persistence.NamedQuery;
|
||||
import javax.persistence.Table;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
@NamedQueries({
|
||||
@NamedQuery(name="hasScope", query="select m from ScopeMappingEntity m where m.client = :client and m.role = :role"),
|
||||
@NamedQuery(name="clientScopeMappings", query="select m from ScopeMappingEntity m where m.client = :client"),
|
||||
@NamedQuery(name="clientScopeMappingIds", query="select m.role.id from ScopeMappingEntity m where m.client = :client"),
|
||||
@NamedQuery(name="deleteScopeMappingByRole", query="delete from ScopeMappingEntity where role = :role"),
|
||||
@NamedQuery(name="deleteScopeMappingByClient", query="delete from ScopeMappingEntity where client = :client")
|
||||
})
|
||||
@Table(name="SCOPE_MAPPING")
|
||||
@Entity
|
||||
@IdClass(ScopeMappingEntity.Key.class)
|
||||
public class ScopeMappingEntity {
|
||||
|
||||
@Id
|
||||
@ManyToOne(fetch= FetchType.LAZY)
|
||||
@JoinColumn(name = "CLIENT_ID")
|
||||
protected ClientEntity client;
|
||||
|
||||
@Id
|
||||
@ManyToOne(fetch= FetchType.LAZY)
|
||||
@JoinColumn(name="ROLE_ID")
|
||||
protected RoleEntity role;
|
||||
|
||||
public ClientEntity getClient() {
|
||||
return client;
|
||||
}
|
||||
|
||||
public void setClient(ClientEntity client) {
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
public RoleEntity getRole() {
|
||||
return role;
|
||||
}
|
||||
|
||||
public void setRole(RoleEntity role) {
|
||||
this.role = role;
|
||||
}
|
||||
|
||||
public static class Key implements Serializable {
|
||||
|
||||
protected ClientEntity client;
|
||||
|
||||
protected RoleEntity role;
|
||||
|
||||
public Key() {
|
||||
}
|
||||
|
||||
public Key(ClientEntity client, RoleEntity role) {
|
||||
this.client = client;
|
||||
this.role = role;
|
||||
}
|
||||
|
||||
public ClientEntity getClient() {
|
||||
return client;
|
||||
}
|
||||
|
||||
public RoleEntity getRole() {
|
||||
return role;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
Key key = (Key) o;
|
||||
|
||||
if (client != null ? !client.getId().equals(key.client != null ? key.client.getId() : null) : key.client != null) return false;
|
||||
if (role != null ? !role.getId().equals(key.role != null ? key.role.getId() : null) : key.role != null) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = client != null ? client.getId().hashCode() : 0;
|
||||
result = 31 * result + (role != null ? role.getId().hashCode() : 0);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null) return false;
|
||||
if (!(o instanceof ScopeMappingEntity)) return false;
|
||||
|
||||
ScopeMappingEntity key = (ScopeMappingEntity) o;
|
||||
|
||||
if (client != null ? !client.getId().equals(key.client != null ? key.client.getId() : null) : key.client != null) return false;
|
||||
if (role != null ? !role.getId().equals(key.role != null ? key.role.getId() : null) : key.role != null) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = client != null ? client.getId().hashCode() : 0;
|
||||
result = 31 * result + (role != null ? role.getId().hashCode() : 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -24,4 +24,139 @@
|
|||
<addPrimaryKey columnNames="USER_SESSION_ID,CLIENT_ID, OFFLINE_FLAG" constraintName="CONSTRAINT_OFFL_CL_SES_PK3" tableName="OFFLINE_CLIENT_SESSION"/>
|
||||
</changeSet>
|
||||
|
||||
<changeSet author="glavoie@gmail.com" id="3.2.0.idx">
|
||||
<createIndex indexName="IDX_ASSOC_POL_ASSOC_POL_ID" tableName="ASSOCIATED_POLICY">
|
||||
<column name="ASSOCIATED_POLICY_ID" type="VARCHAR(36)"/>
|
||||
</createIndex>
|
||||
<createIndex indexName="IDX_AUTH_EXEC_REALM_FLOW" tableName="AUTHENTICATION_EXECUTION">
|
||||
<column name="REALM_ID" type="VARCHAR(36)"/>
|
||||
<column name="FLOW_ID" type="VARCHAR(36)"/>
|
||||
</createIndex>
|
||||
<createIndex indexName="IDX_AUTH_EXEC_FLOW" tableName="AUTHENTICATION_EXECUTION">
|
||||
<column name="FLOW_ID" type="VARCHAR(36)"/>
|
||||
</createIndex>
|
||||
<createIndex indexName="IDX_AUTH_FLOW_REALM" tableName="AUTHENTICATION_FLOW">
|
||||
<column name="REALM_ID" type="VARCHAR(36)"/>
|
||||
</createIndex>
|
||||
<createIndex indexName="IDX_AUTH_CONFIG_REALM" tableName="AUTHENTICATOR_CONFIG">
|
||||
<column name="REALM_ID" type="VARCHAR(36)"/>
|
||||
</createIndex>
|
||||
<createIndex indexName="IDX_CLIENT_CLIENT_TEMPL_ID" tableName="CLIENT">
|
||||
<column name="CLIENT_TEMPLATE_ID" type="VARCHAR(36)"/>
|
||||
</createIndex>
|
||||
<createIndex indexName="IDX_CLIENT_DEF_ROLES_CLIENT" tableName="CLIENT_DEFAULT_ROLES">
|
||||
<column name="CLIENT_ID" type="VARCHAR(36)"/>
|
||||
</createIndex>
|
||||
<createIndex indexName="IDX_CLIENT_ID_PROV_MAP_CLIENT" tableName="CLIENT_IDENTITY_PROV_MAPPING">
|
||||
<column name="CLIENT_ID" type="VARCHAR(36)"/>
|
||||
</createIndex>
|
||||
<createIndex indexName="IDX_CLIENT_SESSION_SESSION" tableName="CLIENT_SESSION">
|
||||
<column name="SESSION_ID" type="VARCHAR(36)"/>
|
||||
</createIndex>
|
||||
<createIndex indexName="IDX_COMPONENT_REALM" tableName="COMPONENT">
|
||||
<column name="REALM_ID" type="VARCHAR(36)"/>
|
||||
</createIndex>
|
||||
<createIndex indexName="IDX_COMPO_CONFIG_COMPO" tableName="COMPONENT_CONFIG">
|
||||
<column name="COMPONENT_ID" type="VARCHAR(36)"/>
|
||||
</createIndex>
|
||||
<createIndex indexName="IDX_COMPOSITE" tableName="COMPOSITE_ROLE">
|
||||
<column name="COMPOSITE" type="VARCHAR(36)"/>
|
||||
</createIndex>
|
||||
<createIndex indexName="IDX_COMPOSITE_CHILD" tableName="COMPOSITE_ROLE">
|
||||
<column name="CHILD_ROLE" type="VARCHAR(36)"/>
|
||||
</createIndex>
|
||||
<createIndex indexName="IDX_CREDENTIAL_ATTR_CRED" tableName="CREDENTIAL_ATTRIBUTE">
|
||||
<column name="CREDENTIAL_ID" type="VARCHAR(36)"/>
|
||||
</createIndex>
|
||||
<createIndex indexName="IDX_FED_CRED_ATTR_CRED" tableName="FED_CREDENTIAL_ATTRIBUTE">
|
||||
<column name="CREDENTIAL_ID" type="VARCHAR(36)"/>
|
||||
</createIndex>
|
||||
<createIndex indexName="IDX_GROUP_ATTR_GROUP" tableName="GROUP_ATTRIBUTE">
|
||||
<column name="GROUP_ID" type="VARCHAR(36)"/>
|
||||
</createIndex>
|
||||
<createIndex indexName="IDX_GROUP_ROLE_MAPP_GROUP" tableName="GROUP_ROLE_MAPPING">
|
||||
<column name="GROUP_ID" type="VARCHAR(36)"/>
|
||||
</createIndex>
|
||||
<createIndex indexName="IDX_IDENT_PROV_REALM" tableName="IDENTITY_PROVIDER">
|
||||
<column name="REALM_ID" type="VARCHAR(36)"/>
|
||||
</createIndex>
|
||||
<createIndex indexName="IDX_ID_PROV_MAPP_REALM" tableName="IDENTITY_PROVIDER_MAPPER">
|
||||
<column name="REALM_ID" type="VARCHAR(36)"/>
|
||||
</createIndex>
|
||||
<createIndex indexName="IDX_KEYCLOAK_ROLE_CLIENT" tableName="KEYCLOAK_ROLE">
|
||||
<column name="CLIENT" type="VARCHAR(36)"/>
|
||||
</createIndex>
|
||||
<createIndex indexName="IDX_KEYCLOAK_ROLE_REALM" tableName="KEYCLOAK_ROLE">
|
||||
<column name="REALM" type="VARCHAR(36)"/>
|
||||
</createIndex>
|
||||
<createIndex indexName="IDX_PROTOCOL_MAPPER_CLIENT" tableName="PROTOCOL_MAPPER">
|
||||
<column name="CLIENT_ID" type="VARCHAR(36)"/>
|
||||
</createIndex>
|
||||
<createIndex indexName="IDX_PROTO_MAPP_CLIENT_TEMPL" tableName="PROTOCOL_MAPPER">
|
||||
<column name="CLIENT_TEMPLATE_ID" type="VARCHAR(36)"/>
|
||||
</createIndex>
|
||||
<createIndex indexName="IDX_REALM_MASTER_ADM_CLI" tableName="REALM">
|
||||
<column name="MASTER_ADMIN_CLIENT" type="VARCHAR(36)"/>
|
||||
</createIndex>
|
||||
<createIndex indexName="IDX_REALM_ATTR_REALM" tableName="REALM_ATTRIBUTE">
|
||||
<column name="REALM_ID" type="VARCHAR(36)"/>
|
||||
</createIndex>
|
||||
<createIndex indexName="IDX_REALM_DEF_GRP_REALM" tableName="REALM_DEFAULT_GROUPS">
|
||||
<column name="REALM_ID" type="VARCHAR(36)"/>
|
||||
</createIndex>
|
||||
<createIndex indexName="IDX_REALM_DEF_ROLES_REALM" tableName="REALM_DEFAULT_ROLES">
|
||||
<column name="REALM_ID" type="VARCHAR(36)"/>
|
||||
</createIndex>
|
||||
<createIndex indexName="IDX_REALM_EVT_TYPES_REALM" tableName="REALM_ENABLED_EVENT_TYPES">
|
||||
<column name="REALM_ID" type="VARCHAR(36)"/>
|
||||
</createIndex>
|
||||
<createIndex indexName="IDX_REALM_EVT_LIST_REALM" tableName="REALM_EVENTS_LISTENERS">
|
||||
<column name="REALM_ID" type="VARCHAR(36)"/>
|
||||
</createIndex>
|
||||
<createIndex indexName="IDX_REALM_SUPP_LOCAL_REALM" tableName="REALM_SUPPORTED_LOCALES">
|
||||
<column name="REALM_ID" type="VARCHAR(36)"/>
|
||||
</createIndex>
|
||||
<createIndex indexName="IDX_REDIR_URI_CLIENT" tableName="REDIRECT_URIS">
|
||||
<column name="CLIENT_ID" type="VARCHAR(36)"/>
|
||||
</createIndex>
|
||||
<createIndex indexName="IDX_REQ_ACT_PROV_REALM" tableName="REQUIRED_ACTION_PROVIDER">
|
||||
<column name="REALM_ID" type="VARCHAR(36)"/>
|
||||
</createIndex>
|
||||
<createIndex indexName="IDX_RES_POLICY_POLICY" tableName="RESOURCE_POLICY">
|
||||
<column name="POLICY_ID" type="VARCHAR(36)"/>
|
||||
</createIndex>
|
||||
<createIndex indexName="IDX_RES_SCOPE_SCOPE" tableName="RESOURCE_SCOPE">
|
||||
<column name="SCOPE_ID" type="VARCHAR(36)"/>
|
||||
</createIndex>
|
||||
<createIndex indexName="IDX_RES_SERV_POL_RES_SERV" tableName="RESOURCE_SERVER_POLICY">
|
||||
<column name="RESOURCE_SERVER_ID" type="VARCHAR(36)"/>
|
||||
</createIndex>
|
||||
<createIndex indexName="IDX_RES_SRV_RES_RES_SRV" tableName="RESOURCE_SERVER_RESOURCE">
|
||||
<column name="RESOURCE_SERVER_ID" type="VARCHAR(36)"/>
|
||||
</createIndex>
|
||||
<createIndex indexName="IDX_RES_SRV_SCOPE_RES_SRV" tableName="RESOURCE_SERVER_SCOPE">
|
||||
<column name="RESOURCE_SERVER_ID" type="VARCHAR(36)"/>
|
||||
</createIndex>
|
||||
<createIndex indexName="IDX_SCOPE_MAPPING_ROLE" tableName="SCOPE_MAPPING">
|
||||
<column name="ROLE_ID" type="VARCHAR(36)"/>
|
||||
</createIndex>
|
||||
<createIndex indexName="IDX_SCOPE_POLICY_POLICY" tableName="SCOPE_POLICY">
|
||||
<column name="POLICY_ID" type="VARCHAR(36)"/>
|
||||
</createIndex>
|
||||
<createIndex indexName="IDX_TEMPL_SCOPE_MAPP_ROLE" tableName="TEMPLATE_SCOPE_MAPPING">
|
||||
<column name="ROLE_ID" type="VARCHAR(36)"/>
|
||||
</createIndex>
|
||||
<createIndex indexName="IDX_USR_FED_MAP_FED_PRV" tableName="USER_FEDERATION_MAPPER">
|
||||
<column name="FEDERATION_PROVIDER_ID" type="VARCHAR(36)"/>
|
||||
</createIndex>
|
||||
<createIndex indexName="IDX_USR_FED_MAP_REALM" tableName="USER_FEDERATION_MAPPER">
|
||||
<column name="REALM_ID" type="VARCHAR(36)"/>
|
||||
</createIndex>
|
||||
<createIndex indexName="IDX_USR_FED_PRV_REALM" tableName="USER_FEDERATION_PROVIDER">
|
||||
<column name="REALM_ID" type="VARCHAR(36)"/>
|
||||
</createIndex>
|
||||
<createIndex indexName="IDX_WEB_ORIG_CLIENT" tableName="WEB_ORIGINS">
|
||||
<column name="CLIENT_ID" type="VARCHAR(36)"/>
|
||||
</createIndex>
|
||||
</changeSet>
|
||||
</databaseChangeLog>
|
|
@ -37,7 +37,6 @@
|
|||
<class>org.keycloak.models.jpa.entities.UserRequiredActionEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.UserAttributeEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.UserRoleMappingEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.ScopeMappingEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.IdentityProviderEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.IdentityProviderMapperEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.ClientIdentityProviderMappingEntity</class>
|
||||
|
|
4
pom.xml
4
pom.xml
|
@ -64,7 +64,7 @@
|
|||
<h2.version>1.3.173</h2.version>
|
||||
<hibernate.entitymanager.version>5.0.7.Final</hibernate.entitymanager.version>
|
||||
<hibernate.javax.persistence.version>1.0.0.Final</hibernate.javax.persistence.version>
|
||||
<infinispan.version>8.1.0.Final</infinispan.version>
|
||||
<infinispan.version>8.2.6.Final</infinispan.version>
|
||||
<jackson.version>2.5.4</jackson.version>
|
||||
<javax.mail.version>1.5.5</javax.mail.version>
|
||||
<jboss.logging.version>3.3.0.Final</jboss.logging.version>
|
||||
|
@ -79,7 +79,7 @@
|
|||
<sun.istack.version>2.21</sun.istack.version>
|
||||
<sun.jaxb.version>2.2.11</sun.jaxb.version>
|
||||
<sun.xsom.version>20140925</sun.xsom.version>
|
||||
<undertow.version>1.3.15.Final</undertow.version>
|
||||
<undertow.version>1.4.11.Final</undertow.version>
|
||||
<xmlsec.version>2.0.5</xmlsec.version>
|
||||
|
||||
<!-- Authorization Drools Policy Provider -->
|
||||
|
|
|
@ -146,7 +146,7 @@ public abstract class AbstractParser implements ParserNamespaceSupport {
|
|||
}
|
||||
|
||||
private boolean valid(String str) {
|
||||
return str != null && str.length() > 0;
|
||||
return str != null && ! str.isEmpty();
|
||||
}
|
||||
|
||||
});
|
||||
|
|
|
@ -308,6 +308,11 @@ public class SAMLParserUtil {
|
|||
return parseNameIDType(xmlEventReader);
|
||||
}
|
||||
} else if (xmlEvent instanceof EndElement) {
|
||||
// consume the end element tag
|
||||
EndElement end = StaxParserUtil.getNextEndElement(xmlEventReader);
|
||||
String endElementTag = StaxParserUtil.getEndElementName(end);
|
||||
if (! StaxParserUtil.matches(end, JBossSAMLConstants.ATTRIBUTE_VALUE.get()))
|
||||
throw logger.parserUnknownEndElement(endElementTag);
|
||||
return "";
|
||||
}
|
||||
|
||||
|
|
|
@ -200,4 +200,20 @@ public class SAMLParserTest {
|
|||
assertThat(parsedObject, instanceOf(EntityDescriptorType.class));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEmptyAttributeValue() throws Exception {
|
||||
try (InputStream st = SAMLParserTest.class.getResourceAsStream("KEYCLOAK-4790-Empty-attribute-value.xml")) {
|
||||
Object parsedObject = parser.parse(st);
|
||||
assertThat(parsedObject, instanceOf(ResponseType.class));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEmptyAttributeValueLast() throws Exception {
|
||||
try (InputStream st = SAMLParserTest.class.getResourceAsStream("KEYCLOAK-4790-Empty-attribute-value-last.xml")) {
|
||||
Object parsedObject = parser.parse(st);
|
||||
assertThat(parsedObject, instanceOf(ResponseType.class));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="_d9e9e102-f048-48fb-a1a3-b5a82d9cd9c3" Version="2.0"
|
||||
IssueInstant="2017-04-24T12:50:14.645Z" Destination="https://y/auth/realms/administration/broker/saml/endpoint"
|
||||
Consent="urn:oasis:names:tc:SAML:2.0:consent:unspecified" InResponseTo="ID_638a829f-7ad2-408e-b3e5-5f2240010ds7f">
|
||||
<saml:Issuer xmlns="urn:oasis:names:tc:SAML:2.0:assertion">https://x/</saml:Issuer>
|
||||
<samlp:Status>
|
||||
<samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>
|
||||
</samlp:Status>
|
||||
<Assertion xmlns="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" ID="_0cceed2a-e409-4faa-a411-c647be748f2b" IssueInstant="2017-04-24T12:50:14.645Z" Version="2.0">
|
||||
<Issuer>https://x/</Issuer>
|
||||
<Subject>
|
||||
<NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName">C=c,OU=ou</NameID>
|
||||
<SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
|
||||
<SubjectConfirmationData InResponseTo="ID_638a829f-7ad2-408e-b3e5-5f224001057f" NotOnOrAfter="2017-04-24T12:55:14.645Z" Recipient="https://y/auth/realms/administration/broker/saml/endpoint"/>
|
||||
</SubjectConfirmation>
|
||||
</Subject>
|
||||
<Conditions NotBefore="2017-04-24T12:45:14.380Z" NotOnOrAfter="2017-04-24T13:45:14.380Z">
|
||||
<AudienceRestriction>
|
||||
<Audience>https://x/auth/realms/administration</Audience>
|
||||
</AudienceRestriction>
|
||||
</Conditions>
|
||||
<AttributeStatement>
|
||||
<Attribute Name="urn:oid:0.9.2342.19200300.100.1.3" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
|
||||
<AttributeValue>aa</AttributeValue>
|
||||
</Attribute>
|
||||
<Attribute Name="urn:oid:0.9.2342.19200300.100.1.2" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
|
||||
<AttributeValue/>
|
||||
</Attribute>
|
||||
</AttributeStatement>
|
||||
<AuthnStatement AuthnInstant="2017-04-24T12:50:14.037Z" SessionIndex="_0cceed2a-e409-4faa-a411-c647be748f2b">
|
||||
<AuthnContext>
|
||||
<AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:X509</AuthnContextClassRef>
|
||||
</AuthnContext>
|
||||
</AuthnStatement>
|
||||
</Assertion>
|
||||
</samlp:Response>
|
|
@ -0,0 +1,35 @@
|
|||
<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="_d9e9e102-f048-48fb-a1a3-b5a82d9cd9c3" Version="2.0"
|
||||
IssueInstant="2017-04-24T12:50:14.645Z" Destination="https://y/auth/realms/administration/broker/saml/endpoint"
|
||||
Consent="urn:oasis:names:tc:SAML:2.0:consent:unspecified" InResponseTo="ID_638a829f-7ad2-408e-b3e5-5f2240010ds7f">
|
||||
<saml:Issuer xmlns="urn:oasis:names:tc:SAML:2.0:assertion">https://x/</saml:Issuer>
|
||||
<samlp:Status>
|
||||
<samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>
|
||||
</samlp:Status>
|
||||
<Assertion xmlns="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" ID="_0cceed2a-e409-4faa-a411-c647be748f2b" IssueInstant="2017-04-24T12:50:14.645Z" Version="2.0">
|
||||
<Issuer>https://x/</Issuer>
|
||||
<Subject>
|
||||
<NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName">C=c,OU=ou</NameID>
|
||||
<SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
|
||||
<SubjectConfirmationData InResponseTo="ID_638a829f-7ad2-408e-b3e5-5f224001057f" NotOnOrAfter="2017-04-24T12:55:14.645Z" Recipient="https://y/auth/realms/administration/broker/saml/endpoint"/>
|
||||
</SubjectConfirmation>
|
||||
</Subject>
|
||||
<Conditions NotBefore="2017-04-24T12:45:14.380Z" NotOnOrAfter="2017-04-24T13:45:14.380Z">
|
||||
<AudienceRestriction>
|
||||
<Audience>https://x/auth/realms/administration</Audience>
|
||||
</AudienceRestriction>
|
||||
</Conditions>
|
||||
<AttributeStatement>
|
||||
<Attribute Name="urn:oid:0.9.2342.19200300.100.1.2" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
|
||||
<AttributeValue/>
|
||||
</Attribute>
|
||||
<Attribute Name="urn:oid:0.9.2342.19200300.100.1.3" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
|
||||
<AttributeValue>aa</AttributeValue>
|
||||
</Attribute>
|
||||
</AttributeStatement>
|
||||
<AuthnStatement AuthnInstant="2017-04-24T12:50:14.037Z" SessionIndex="_0cceed2a-e409-4faa-a411-c647be748f2b">
|
||||
<AuthnContext>
|
||||
<AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:X509</AuthnContextClassRef>
|
||||
</AuthnContext>
|
||||
</AuthnStatement>
|
||||
</Assertion>
|
||||
</samlp:Response>
|
|
@ -58,7 +58,7 @@ public interface AuthenticationFlowContext extends AbstractAuthenticationFlowCon
|
|||
|
||||
|
||||
/**
|
||||
* ClientSessionModel attached to this flow
|
||||
* AuthenticationSessionModel attached to this flow
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
|
@ -74,7 +74,7 @@ public interface AuthenticationFlowContext extends AbstractAuthenticationFlowCon
|
|||
/**
|
||||
* Get the action URL for the required action.
|
||||
*
|
||||
* @param code client session access code
|
||||
* @param code authentication session access code
|
||||
* @return
|
||||
*/
|
||||
URI getActionUrl(String code);
|
||||
|
@ -114,7 +114,7 @@ public interface AuthenticationFlowContext extends AbstractAuthenticationFlowCon
|
|||
void resetFlow(Runnable afterResetListener);
|
||||
|
||||
/**
|
||||
* Fork the current flow. The client session will be cloned and set to point at the realm's browser login flow. The Response will be the result
|
||||
* Fork the current flow. The authentication session will be cloned and set to point at the realm's browser login flow. The Response will be the result
|
||||
* of this fork. The previous flow will still be set at the current execution. This is used by reset password when it sends an email.
|
||||
* It sends an email linking to the current flow and redirects the browser to a new browser login flow.
|
||||
*
|
||||
|
@ -125,7 +125,7 @@ public interface AuthenticationFlowContext extends AbstractAuthenticationFlowCon
|
|||
void fork();
|
||||
|
||||
/**
|
||||
* Fork the current flow. The client session will be cloned and set to point at the realm's browser login flow. The Response will be the result
|
||||
* Fork the current flow. The authentication session will be cloned and set to point at the realm's browser login flow. The Response will be the result
|
||||
* of this fork. The previous flow will still be set at the current execution. This is used by reset password when it sends an email.
|
||||
* It sends an email linking to the current flow and redirects the browser to a new browser login flow.
|
||||
*
|
||||
|
@ -135,7 +135,7 @@ public interface AuthenticationFlowContext extends AbstractAuthenticationFlowCon
|
|||
*/
|
||||
void forkWithSuccessMessage(FormMessage message);
|
||||
/**
|
||||
* Fork the current flow. The client session will be cloned and set to point at the realm's browser login flow. The Response will be the result
|
||||
* Fork the current flow. The authentication session will be cloned and set to point at the realm's browser login flow. The Response will be the result
|
||||
* of this fork. The previous flow will still be set at the current execution. This is used by reset password when it sends an email.
|
||||
* It sends an email linking to the current flow and redirects the browser to a new browser login flow.
|
||||
*
|
||||
|
|
|
@ -62,7 +62,7 @@ public enum FlowStatus {
|
|||
ATTEMPTED,
|
||||
|
||||
/**
|
||||
* This flow is being forked. The current client session is being cloned, reset, and redirected to browser login.
|
||||
* This flow is being forked. The current authentication session is being cloned, reset, and redirected to browser login.
|
||||
*
|
||||
*/
|
||||
FORK,
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
package org.keycloak.broker.provider;
|
||||
|
||||
import org.jboss.resteasy.spi.HttpRequest;
|
||||
import org.keycloak.broker.provider.util.IdentityBrokerState;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.sessions.AuthenticationSessionModel;
|
||||
|
@ -30,13 +31,13 @@ public class AuthenticationRequest {
|
|||
|
||||
private final KeycloakSession session;
|
||||
private final UriInfo uriInfo;
|
||||
private final String state;
|
||||
private final IdentityBrokerState state;
|
||||
private final HttpRequest httpRequest;
|
||||
private final RealmModel realm;
|
||||
private final String redirectUri;
|
||||
private final AuthenticationSessionModel authSession;
|
||||
|
||||
public AuthenticationRequest(KeycloakSession session, RealmModel realm, AuthenticationSessionModel authSession, HttpRequest httpRequest, UriInfo uriInfo, String state, String redirectUri) {
|
||||
public AuthenticationRequest(KeycloakSession session, RealmModel realm, AuthenticationSessionModel authSession, HttpRequest httpRequest, UriInfo uriInfo, IdentityBrokerState state, String redirectUri) {
|
||||
this.session = session;
|
||||
this.realm = realm;
|
||||
this.httpRequest = httpRequest;
|
||||
|
@ -54,7 +55,7 @@ public class AuthenticationRequest {
|
|||
return this.uriInfo;
|
||||
}
|
||||
|
||||
public String getState() {
|
||||
public IdentityBrokerState getState() {
|
||||
return this.state;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,90 @@
|
|||
/*
|
||||
* 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.broker.provider.util;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Encapsulates parsing logic related to state passed to identity provider in "state" (or RelayState) parameter
|
||||
*
|
||||
* Not Thread-safe
|
||||
*
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class IdentityBrokerState {
|
||||
|
||||
private String decodedState;
|
||||
private String clientId;
|
||||
private String encodedState;
|
||||
|
||||
private IdentityBrokerState() {
|
||||
}
|
||||
|
||||
public static IdentityBrokerState decoded(String decodedState, String clientId) {
|
||||
IdentityBrokerState state = new IdentityBrokerState();
|
||||
state.decodedState = decodedState;
|
||||
state.clientId = clientId;
|
||||
return state;
|
||||
}
|
||||
|
||||
public static IdentityBrokerState encoded(String encodedState) {
|
||||
IdentityBrokerState state = new IdentityBrokerState();
|
||||
state.encodedState = encodedState;
|
||||
return state;
|
||||
}
|
||||
|
||||
|
||||
public String getDecodedState() {
|
||||
if (decodedState == null) {
|
||||
decode();
|
||||
}
|
||||
return decodedState;
|
||||
}
|
||||
|
||||
public String getClientId() {
|
||||
if (decodedState == null) {
|
||||
decode();
|
||||
}
|
||||
return clientId;
|
||||
}
|
||||
|
||||
public String getEncodedState() {
|
||||
if (encodedState == null) {
|
||||
encode();
|
||||
}
|
||||
return encodedState;
|
||||
}
|
||||
|
||||
|
||||
private void decode() {
|
||||
String[] decoded = DOT.split(encodedState, 0);
|
||||
decodedState = decoded[0];
|
||||
if (decoded.length > 0) {
|
||||
clientId = decoded[1];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void encode() {
|
||||
encodedState = decodedState + "." + clientId;
|
||||
}
|
||||
|
||||
private static final Pattern DOT = Pattern.compile("\\.");
|
||||
|
||||
|
||||
}
|
|
@ -22,7 +22,6 @@ import org.keycloak.models.RoleModel;
|
|||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.utils.FormMessage;
|
||||
import org.keycloak.provider.Provider;
|
||||
import org.keycloak.sessions.AuthenticationSessionModel;
|
||||
|
||||
import javax.ws.rs.core.MultivaluedMap;
|
||||
import javax.ws.rs.core.Response;
|
||||
|
|
|
@ -52,8 +52,12 @@ public interface Constants {
|
|||
int DEFAULT_OFFLINE_SESSION_IDLE_TIMEOUT = 2592000;
|
||||
|
||||
String VERIFY_EMAIL_KEY = "VERIFY_EMAIL_KEY";
|
||||
String EXECUTION = "execution";
|
||||
String CLIENT_ID = "client_id";
|
||||
String KEY = "key";
|
||||
|
||||
String SKIP_LINK = "skipLink";
|
||||
|
||||
// Prefix for user attributes used in various "context"data maps
|
||||
String USER_ATTRIBUTES_PREFIX = "user.attributes.";
|
||||
|
||||
|
|
|
@ -54,7 +54,7 @@ public class ComponentUtil {
|
|||
return getComponentFactory(session, component.getProviderType(), component.getProviderId());
|
||||
}
|
||||
|
||||
private static Map<String, ProviderConfigProperty> getComponentConfigProperties(KeycloakSession session, String providerType, String providerId) {
|
||||
public static Map<String, ProviderConfigProperty> getComponentConfigProperties(KeycloakSession session, String providerType, String providerId) {
|
||||
try {
|
||||
ComponentFactory componentFactory = getComponentFactory(session, providerType, providerId);
|
||||
List<ProviderConfigProperty> l = componentFactory.getConfigProperties();
|
||||
|
|
|
@ -872,8 +872,6 @@ public class ModelToRepresentation {
|
|||
return scope;
|
||||
}).collect(Collectors.toSet()));
|
||||
|
||||
resource.setTypedScopes(new ArrayList<>());
|
||||
|
||||
if (resource.getType() != null) {
|
||||
ResourceStore resourceStore = authorization.getStoreFactory().getResourceStore();
|
||||
for (Resource typed : resourceStore.findByType(resource.getType(), resourceServer.getId())) {
|
||||
|
|
|
@ -151,6 +151,8 @@ public class RepresentationToModel {
|
|||
if (rep.getMaxDeltaTimeSeconds() != null) newRealm.setMaxDeltaTimeSeconds(rep.getMaxDeltaTimeSeconds());
|
||||
if (rep.getFailureFactor() != null) newRealm.setFailureFactor(rep.getFailureFactor());
|
||||
if (rep.isEventsEnabled() != null) newRealm.setEventsEnabled(rep.isEventsEnabled());
|
||||
if (rep.getEnabledEventTypes() != null)
|
||||
newRealm.setEnabledEventTypes(new HashSet<>(rep.getEnabledEventTypes()));
|
||||
if (rep.getEventsExpiration() != null) newRealm.setEventsExpiration(rep.getEventsExpiration());
|
||||
if (rep.getEventsListeners() != null) newRealm.setEventsListeners(new HashSet<>(rep.getEventsListeners()));
|
||||
if (rep.isAdminEventsEnabled() != null) newRealm.setAdminEventsEnabled(rep.isAdminEventsEnabled());
|
||||
|
|
|
@ -17,15 +17,18 @@
|
|||
|
||||
package org.keycloak.models.utils;
|
||||
|
||||
import org.keycloak.common.util.MultivaluedHashMap;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.provider.ProviderConfigProperty;
|
||||
import org.keycloak.representations.idm.ClientRepresentation;
|
||||
import org.keycloak.representations.idm.ComponentExportRepresentation;
|
||||
import org.keycloak.representations.idm.ComponentRepresentation;
|
||||
import org.keycloak.representations.idm.CredentialRepresentation;
|
||||
import org.keycloak.representations.idm.IdentityProviderRepresentation;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.representations.idm.UserRepresentation;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
@ -70,4 +73,86 @@ public class StripSecretsUtils {
|
|||
return rep;
|
||||
}
|
||||
|
||||
public static RealmRepresentation stripForExport(KeycloakSession session, RealmRepresentation rep) {
|
||||
strip(rep);
|
||||
|
||||
List<ClientRepresentation> clients = rep.getClients();
|
||||
if (clients != null) {
|
||||
for (ClientRepresentation c : clients) {
|
||||
strip(c);
|
||||
}
|
||||
}
|
||||
List<IdentityProviderRepresentation> providers = rep.getIdentityProviders();
|
||||
if (providers != null) {
|
||||
for (IdentityProviderRepresentation r : providers) {
|
||||
strip(r);
|
||||
}
|
||||
}
|
||||
|
||||
MultivaluedHashMap<String, ComponentExportRepresentation> components = rep.getComponents();
|
||||
if (components != null) {
|
||||
for (Map.Entry<String, List<ComponentExportRepresentation>> ent : components.entrySet()) {
|
||||
for (ComponentExportRepresentation c : ent.getValue()) {
|
||||
strip(session, ent.getKey(), c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List<UserRepresentation> users = rep.getUsers();
|
||||
if (users != null) {
|
||||
for (UserRepresentation u: users) {
|
||||
strip(u);
|
||||
}
|
||||
}
|
||||
|
||||
users = rep.getFederatedUsers();
|
||||
if (users != null) {
|
||||
for (UserRepresentation u: users) {
|
||||
strip(u);
|
||||
}
|
||||
}
|
||||
|
||||
return rep;
|
||||
}
|
||||
|
||||
public static UserRepresentation strip(UserRepresentation user) {
|
||||
user.setCredentials(null);
|
||||
return user;
|
||||
}
|
||||
|
||||
public static ClientRepresentation strip(ClientRepresentation rep) {
|
||||
if (rep.getSecret() != null) {
|
||||
rep.setSecret(ComponentRepresentation.SECRET_VALUE);
|
||||
}
|
||||
return rep;
|
||||
}
|
||||
|
||||
public static ComponentExportRepresentation strip(KeycloakSession session, String providerType, ComponentExportRepresentation rep) {
|
||||
Map<String, ProviderConfigProperty> configProperties = ComponentUtil.getComponentConfigProperties(session, providerType, rep.getProviderId());
|
||||
if (rep.getConfig() == null) {
|
||||
return rep;
|
||||
}
|
||||
|
||||
Iterator<Map.Entry<String, List<String>>> itr = rep.getConfig().entrySet().iterator();
|
||||
while (itr.hasNext()) {
|
||||
Map.Entry<String, List<String>> next = itr.next();
|
||||
ProviderConfigProperty configProperty = configProperties.get(next.getKey());
|
||||
if (configProperty != null) {
|
||||
if (configProperty.isSecret()) {
|
||||
next.setValue(Collections.singletonList(ComponentRepresentation.SECRET_VALUE));
|
||||
}
|
||||
} else {
|
||||
itr.remove();
|
||||
}
|
||||
}
|
||||
|
||||
MultivaluedHashMap<String, ComponentExportRepresentation> sub = rep.getSubComponents();
|
||||
for (Map.Entry<String, List<ComponentExportRepresentation>> ent: sub.entrySet()) {
|
||||
for (ComponentExportRepresentation c: ent.getValue()) {
|
||||
strip(session, ent.getKey(), c);
|
||||
}
|
||||
}
|
||||
return rep;
|
||||
}
|
||||
|
||||
}
|
|
@ -91,7 +91,7 @@ public class HashAlgorithmPasswordPolicyProviderFactory implements PasswordPolic
|
|||
String providerId = value != null && value.length() > 0 ? value : PasswordPolicy.HASH_ALGORITHM_DEFAULT;
|
||||
PasswordHashProvider provider = session.getProvider(PasswordHashProvider.class, providerId);
|
||||
if (provider == null) {
|
||||
throw new ModelException("Password hashing provider not found");
|
||||
throw new PasswordPolicyConfigException("Password hashing provider not found");
|
||||
}
|
||||
return providerId;
|
||||
}
|
||||
|
|
|
@ -73,7 +73,7 @@ public class HistoryPasswordPolicyProvider implements PasswordPolicyProvider {
|
|||
|
||||
@Override
|
||||
public Object parseConfig(String value) {
|
||||
return value != null ? Integer.parseInt(value) : HistoryPasswordPolicyProviderFactory.DEFAULT_VALUE;
|
||||
return parseInteger(value, HistoryPasswordPolicyProviderFactory.DEFAULT_VALUE);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -47,7 +47,7 @@ public class LengthPasswordPolicyProvider implements PasswordPolicyProvider {
|
|||
|
||||
@Override
|
||||
public Object parseConfig(String value) {
|
||||
return value != null ? Integer.parseInt(value) : 8;
|
||||
return parseInteger(value, 8);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -53,7 +53,7 @@ public class LowerCasePasswordPolicyProvider implements PasswordPolicyProvider {
|
|||
|
||||
@Override
|
||||
public Object parseConfig(String value) {
|
||||
return value != null ? Integer.parseInt(value) : 1;
|
||||
return parseInteger(value, 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -23,6 +23,7 @@ import org.keycloak.models.UserModel;
|
|||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.regex.PatternSyntaxException;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
|
@ -55,9 +56,13 @@ public class RegexPatternsPasswordPolicyProvider implements PasswordPolicyProvid
|
|||
@Override
|
||||
public Object parseConfig(String value) {
|
||||
if (value == null) {
|
||||
throw new IllegalArgumentException("Config required");
|
||||
throw new PasswordPolicyConfigException("Config required");
|
||||
}
|
||||
try {
|
||||
return Pattern.compile(value);
|
||||
} catch (PatternSyntaxException e) {
|
||||
throw new PasswordPolicyConfigException("Not a valid regular expression");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -53,7 +53,7 @@ public class SpecialCharsPasswordPolicyProvider implements PasswordPolicyProvide
|
|||
|
||||
@Override
|
||||
public Object parseConfig(String value) {
|
||||
return value != null ? Integer.parseInt(value) : 1;
|
||||
return parseInteger(value, 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -53,7 +53,7 @@ public class UpperCasePasswordPolicyProvider implements PasswordPolicyProvider {
|
|||
|
||||
@Override
|
||||
public Object parseConfig(String value) {
|
||||
return value != null ? Integer.parseInt(value) : 1;
|
||||
return parseInteger(value, 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -1,81 +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;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.keycloak.sessions.CommonClientSessionModel;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public interface ClientSessionModel extends CommonClientSessionModel {
|
||||
|
||||
public UserSessionModel getUserSession();
|
||||
public void setUserSession(UserSessionModel userSession);
|
||||
|
||||
public String getRedirectUri();
|
||||
public void setRedirectUri(String uri);
|
||||
|
||||
public Map<String, ExecutionStatus> getExecutionStatus();
|
||||
public void setExecutionStatus(String authenticator, ExecutionStatus status);
|
||||
public void clearExecutionStatus();
|
||||
public UserModel getAuthenticatedUser();
|
||||
public void setAuthenticatedUser(UserModel user);
|
||||
|
||||
/**
|
||||
* Required actions that are attached to this client session.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
Set<String> getRequiredActions();
|
||||
|
||||
void addRequiredAction(String action);
|
||||
|
||||
void removeRequiredAction(String action);
|
||||
|
||||
void addRequiredAction(UserModel.RequiredAction action);
|
||||
|
||||
void removeRequiredAction(UserModel.RequiredAction action);
|
||||
|
||||
|
||||
/**
|
||||
* These are notes you want applied to the UserSessionModel when the client session is attached to it.
|
||||
*
|
||||
* @param name
|
||||
* @param value
|
||||
*/
|
||||
public void setUserSessionNote(String name, String value);
|
||||
|
||||
/**
|
||||
* These are notes you want applied to the UserSessionModel when the client session is attached to it.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Map<String, String> getUserSessionNotes();
|
||||
|
||||
public void clearUserSessionNotes();
|
||||
|
||||
public String getNote(String name);
|
||||
public void setNote(String name, String value);
|
||||
public void removeNote(String name);
|
||||
public Map<String, String> getNotes();
|
||||
|
||||
|
||||
}
|
|
@ -17,6 +17,7 @@
|
|||
|
||||
package org.keycloak.models;
|
||||
|
||||
import org.keycloak.policy.PasswordPolicyConfigException;
|
||||
import org.keycloak.policy.PasswordPolicyProvider;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
@ -68,10 +69,17 @@ public class PasswordPolicy implements Serializable {
|
|||
|
||||
PasswordPolicyProvider provider = session.getProvider(PasswordPolicyProvider.class, key);
|
||||
if (provider == null) {
|
||||
throw new IllegalArgumentException("Unsupported policy");
|
||||
throw new PasswordPolicyConfigException("Password policy not found");
|
||||
}
|
||||
|
||||
policyConfig.put(key, provider.parseConfig(config));
|
||||
Object o;
|
||||
try {
|
||||
o = provider.parseConfig(config);
|
||||
} catch (PasswordPolicyConfigException e) {
|
||||
throw new ModelException("Invalid config for " + key + ": " + e.getMessage());
|
||||
}
|
||||
|
||||
policyConfig.put(key, o);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
package org.keycloak.policy;
|
||||
|
||||
import org.keycloak.models.ModelException;
|
||||
|
||||
/**
|
||||
* Created by st on 23/05/17.
|
||||
*/
|
||||
public class PasswordPolicyConfigException extends ModelException {
|
||||
|
||||
public PasswordPolicyConfigException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
}
|
|
@ -33,4 +33,12 @@ public interface PasswordPolicyProvider extends Provider {
|
|||
PolicyError validate(String user, String password);
|
||||
Object parseConfig(String value);
|
||||
|
||||
default Integer parseInteger(String value, Integer defaultValue) {
|
||||
try {
|
||||
return value != null ? Integer.parseInt(value) : defaultValue;
|
||||
} catch (NumberFormatException e) {
|
||||
throw new PasswordPolicyConfigException("Not a valid number");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -33,6 +33,7 @@ import org.keycloak.models.AuthenticationFlowModel;
|
|||
import org.keycloak.models.AuthenticatorConfigModel;
|
||||
import org.keycloak.models.AuthenticatedClientSessionModel;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.Constants;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
|
@ -485,15 +486,17 @@ public class AuthenticationProcessor {
|
|||
return LoginActionsService.loginActionsBaseUrl(getUriInfo())
|
||||
.path(AuthenticationProcessor.this.flowPath)
|
||||
.queryParam(OAuth2Constants.CODE, code)
|
||||
.queryParam("execution", getExecution().getId())
|
||||
.queryParam(Constants.EXECUTION, getExecution().getId())
|
||||
.queryParam(Constants.CLIENT_ID, getAuthenticationSession().getClient().getClientId())
|
||||
.build(getRealm().getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public URI getActionTokenUrl(String tokenString) {
|
||||
return LoginActionsService.actionTokenProcessor(getUriInfo())
|
||||
.queryParam("key", tokenString)
|
||||
.queryParam("execution", getExecution().getId())
|
||||
.queryParam(Constants.KEY, tokenString)
|
||||
.queryParam(Constants.EXECUTION, getExecution().getId())
|
||||
.queryParam(Constants.CLIENT_ID, getAuthenticationSession().getClient().getClientId())
|
||||
.build(getRealm().getName());
|
||||
}
|
||||
|
||||
|
@ -501,7 +504,8 @@ public class AuthenticationProcessor {
|
|||
public URI getRefreshExecutionUrl() {
|
||||
return LoginActionsService.loginActionsBaseUrl(getUriInfo())
|
||||
.path(AuthenticationProcessor.this.flowPath)
|
||||
.queryParam("execution", getExecution().getId())
|
||||
.queryParam(Constants.EXECUTION, getExecution().getId())
|
||||
.queryParam(Constants.CLIENT_ID, getAuthenticationSession().getClient().getClientId())
|
||||
.build(getRealm().getName());
|
||||
}
|
||||
|
||||
|
|
|
@ -24,6 +24,8 @@ import org.keycloak.events.EventBuilder;
|
|||
import org.keycloak.forms.login.LoginFormsProvider;
|
||||
import org.keycloak.models.AuthenticationExecutionModel;
|
||||
import org.keycloak.models.AuthenticatorConfigModel;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.Constants;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
|
@ -247,9 +249,11 @@ public class FormAuthenticationFlow implements AuthenticationFlow {
|
|||
}
|
||||
|
||||
public URI getActionUrl(String executionId, String code) {
|
||||
ClientModel client = processor.getAuthenticationSession().getClient();
|
||||
return LoginActionsService.registrationFormProcessor(processor.getUriInfo())
|
||||
.queryParam(OAuth2Constants.CODE, code)
|
||||
.queryParam("execution", executionId)
|
||||
.queryParam(Constants.EXECUTION, executionId)
|
||||
.queryParam(Constants.CLIENT_ID, client.getClientId())
|
||||
.build(processor.getRealm().getName());
|
||||
}
|
||||
|
||||
|
|
|
@ -23,6 +23,8 @@ import org.keycloak.common.ClientConnection;
|
|||
import org.keycloak.common.util.Time;
|
||||
import org.keycloak.events.EventBuilder;
|
||||
import org.keycloak.forms.login.LoginFormsProvider;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.Constants;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
|
@ -132,9 +134,11 @@ public class RequiredActionContextResult implements RequiredActionContext {
|
|||
|
||||
@Override
|
||||
public URI getActionUrl(String code) {
|
||||
ClientModel client = authenticationSession.getClient();
|
||||
return LoginActionsService.requiredActionProcessor(getUriInfo())
|
||||
.queryParam(OAuth2Constants.CODE, code)
|
||||
.queryParam("execution", factory.getId())
|
||||
.queryParam(Constants.EXECUTION, factory.getId())
|
||||
.queryParam(Constants.CLIENT_ID, client.getClientId())
|
||||
.build(getRealm().getName());
|
||||
}
|
||||
|
||||
|
|
|
@ -26,7 +26,6 @@ import org.keycloak.representations.JsonWebToken;
|
|||
import org.keycloak.services.Urls;
|
||||
import org.keycloak.services.managers.AuthenticationSessionManager;
|
||||
import org.keycloak.sessions.AuthenticationSessionModel;
|
||||
import java.util.function.Function;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.UriBuilderException;
|
||||
import javax.ws.rs.core.UriInfo;
|
||||
|
@ -45,7 +44,7 @@ public class ActionTokenContext<T extends JsonWebToken> {
|
|||
|
||||
@FunctionalInterface
|
||||
public interface ProcessBrokerFlow {
|
||||
Response brokerLoginFlow(String code, String execution, String flowPath);
|
||||
Response brokerLoginFlow(String code, String execution, String clientId, String flowPath);
|
||||
};
|
||||
|
||||
private final KeycloakSession session;
|
||||
|
@ -113,7 +112,7 @@ public class ActionTokenContext<T extends JsonWebToken> {
|
|||
ClientModel client = realm.getClientByClientId(clientId == null ? Constants.ACCOUNT_MANAGEMENT_CLIENT_ID : clientId);
|
||||
|
||||
authSession = new AuthenticationSessionManager(session).createAuthenticationSession(realm, client, true);
|
||||
authSession.setAction(ClientSessionModel.Action.AUTHENTICATE.name());
|
||||
authSession.setAction(AuthenticationSessionModel.Action.AUTHENTICATE.name());
|
||||
authSession.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
|
||||
String redirectUri = Urls.accountBase(uriInfo.getBaseUri()).path("/").build(realm.getName()).toString();
|
||||
authSession.setRedirectUri(redirectUri);
|
||||
|
@ -158,6 +157,7 @@ public class ActionTokenContext<T extends JsonWebToken> {
|
|||
}
|
||||
|
||||
public Response brokerFlow(String code, String flowPath) {
|
||||
return processBrokerFlow.brokerLoginFlow(code, getExecutionId(), flowPath);
|
||||
ClientModel client = authenticationSession.getClient();
|
||||
return processBrokerFlow.brokerLoginFlow(code, getExecutionId(), client.getClientId(), flowPath);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ import org.keycloak.authentication.actiontoken.*;
|
|||
import org.keycloak.authentication.authenticators.broker.IdpEmailVerificationAuthenticator;
|
||||
import org.keycloak.events.*;
|
||||
import org.keycloak.forms.login.LoginFormsProvider;
|
||||
import org.keycloak.models.Constants;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.services.managers.AuthenticationSessionManager;
|
||||
import org.keycloak.services.messages.Messages;
|
||||
|
@ -86,7 +87,7 @@ public class IdpVerifyAccountLinkActionTokenHandler extends AbstractActionTokenH
|
|||
|
||||
return tokenContext.getSession().getProvider(LoginFormsProvider.class)
|
||||
.setSuccess(Messages.IDENTITY_PROVIDER_LINK_SUCCESS, token.getIdentityProviderAlias(), token.getIdentityProviderUsername())
|
||||
.setAttribute("skipLink", true)
|
||||
.setAttribute(Constants.SKIP_LINK, true)
|
||||
.createInfoPage();
|
||||
}
|
||||
|
||||
|
|
|
@ -132,7 +132,10 @@ public class IdpEmailVerificationAuthenticator extends AbstractIdpAuthenticator
|
|||
brokerContext.getUsername(), brokerContext.getIdpConfig().getAlias()
|
||||
);
|
||||
UriBuilder builder = Urls.actionTokenBuilder(uriInfo.getBaseUri(), token.serialize(session, realm, uriInfo));
|
||||
String link = builder.queryParam("execution", context.getExecution().getId()).build(realm.getName()).toString();
|
||||
String link = builder
|
||||
.queryParam(Constants.EXECUTION, context.getExecution().getId())
|
||||
.queryParam(Constants.CLIENT_ID, context.getExecution().getId())
|
||||
.build(realm.getName()).toString();
|
||||
long expirationInMinutes = TimeUnit.SECONDS.toMinutes(validityInSecs);
|
||||
|
||||
try {
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue