Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Bill Burke 2017-04-26 14:39:45 -04:00
commit 2276f99d54
190 changed files with 7898 additions and 804 deletions

View file

@ -29,6 +29,7 @@ import org.keycloak.KeycloakSecurityContext;
import org.keycloak.adapters.OIDCHttpFacade;
import org.keycloak.adapters.spi.HttpFacade.Request;
import org.keycloak.authorization.client.AuthzClient;
import org.keycloak.authorization.client.ClientAuthorizationContext;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig;
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.EnforcementMode;
@ -203,7 +204,7 @@ public abstract class AbstractPolicyEnforcer {
}
private AuthorizationContext createEmptyAuthorizationContext(final boolean granted) {
return new AuthorizationContext() {
return new ClientAuthorizationContext(authzClient) {
@Override
public boolean hasPermission(String resourceName, String scopeName) {
return granted;
@ -252,7 +253,7 @@ public abstract class AbstractPolicyEnforcer {
}
private AuthorizationContext createAuthorizationContext(AccessToken accessToken) {
return new AuthorizationContext(accessToken, this.paths);
return new ClientAuthorizationContext(accessToken, this.paths, authzClient);
}
private boolean isResourcePermission(PathConfig actualPathConfig, Permission permission) {

View file

@ -21,7 +21,9 @@ import org.jboss.logging.Logger;
import org.keycloak.AuthorizationContext;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.OIDCHttpFacade;
import org.keycloak.adapters.authentication.ClientCredentialsProviderUtils;
import org.keycloak.authorization.client.AuthzClient;
import org.keycloak.authorization.client.ClientAuthenticator;
import org.keycloak.authorization.client.Configuration;
import org.keycloak.authorization.client.representation.RegistrationResponse;
import org.keycloak.authorization.client.representation.ResourceRepresentation;
@ -56,7 +58,12 @@ public class PolicyEnforcer {
public PolicyEnforcer(KeycloakDeployment deployment, AdapterConfig adapterConfig) {
this.deployment = deployment;
this.enforcerConfig = adapterConfig.getPolicyEnforcerConfig();
this.authzClient = AuthzClient.create(new Configuration(adapterConfig.getAuthServerUrl(), adapterConfig.getRealm(), adapterConfig.getResource(), adapterConfig.getCredentials(), deployment.getClient()));
this.authzClient = AuthzClient.create(new Configuration(adapterConfig.getAuthServerUrl(), adapterConfig.getRealm(), adapterConfig.getResource(), adapterConfig.getCredentials(), deployment.getClient()), new ClientAuthenticator() {
@Override
public void configureClientCredentials(HashMap<String, String> requestParams, HashMap<String, String> requestHeaders) {
ClientCredentialsProviderUtils.setClientCredentials(PolicyEnforcer.this.deployment, requestHeaders, requestParams);
}
});
this.pathMatcher = new PathMatcher(this.authzClient);
this.paths = configurePaths(this.authzClient.protection().resource(), this.enforcerConfig);

View file

@ -34,6 +34,7 @@
<module>jetty-core</module>
<module>jetty8.1</module>
<module>jetty9.2</module>
<module>jetty9.3</module>
</modules>
<profiles>
@ -46,7 +47,6 @@
</activation>
<modules>
<module>jetty9.1</module>
<module>jetty9.3</module>
<module>jetty9.4</module>
</modules>
</profile>

View file

@ -17,6 +17,7 @@
package org.keycloak.adapters.saml;
import org.keycloak.adapters.saml.SamlDeployment.IDP.SingleSignOnService;
import org.jboss.logging.Logger;
import org.keycloak.adapters.spi.AuthChallenge;
import org.keycloak.adapters.spi.HttpFacade;
@ -95,21 +96,22 @@ public abstract class AbstractInitiateLogin implements AuthChallenge {
nameIDPolicyFormat = JBossSAMLURIConstants.NAMEID_FORMAT_PERSISTENT.get();
}
SingleSignOnService sso = deployment.getIDP().getSingleSignOnService();
SAML2AuthnRequestBuilder authnRequestBuilder = new SAML2AuthnRequestBuilder()
.destination(deployment.getIDP().getSingleSignOnService().getRequestBindingUrl())
.destination(sso.getRequestBindingUrl())
.issuer(issuerURL)
.forceAuthn(deployment.isForceAuthentication()).isPassive(deployment.isIsPassive())
.nameIdPolicy(SAML2NameIDPolicyBuilder.format(nameIDPolicyFormat));
if (deployment.getIDP().getSingleSignOnService().getResponseBinding() != null) {
if (sso.getResponseBinding() != null) {
String protocolBinding = JBossSAMLURIConstants.SAML_HTTP_REDIRECT_BINDING.get();
if (deployment.getIDP().getSingleSignOnService().getResponseBinding() == SamlDeployment.Binding.POST) {
if (sso.getResponseBinding() == SamlDeployment.Binding.POST) {
protocolBinding = JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.get();
}
authnRequestBuilder.protocolBinding(protocolBinding);
}
if (deployment.getAssertionConsumerServiceUrl() != null) {
authnRequestBuilder.assertionConsumerUrl(deployment.getAssertionConsumerServiceUrl());
if (sso.getAssertionConsumerServiceUrl() != null) {
authnRequestBuilder.assertionConsumerUrl(sso.getAssertionConsumerServiceUrl());
}
return authnRequestBuilder;
}

View file

@ -31,6 +31,7 @@ import org.keycloak.adapters.saml.rotation.SamlDescriptorPublicKeyLocator;
import org.keycloak.rotation.CompositeKeyLocator;
import org.keycloak.rotation.HardcodedKeyLocator;
import org.keycloak.rotation.KeyLocator;
import java.net.URI;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@ -45,6 +46,7 @@ public class DefaultSamlDeployment implements SamlDeployment {
private Binding requestBinding;
private Binding responseBinding;
private String requestBindingUrl;
private URI assertionConsumerServiceUrl;
@Override
public boolean signRequest() {
@ -76,6 +78,15 @@ public class DefaultSamlDeployment implements SamlDeployment {
return requestBindingUrl;
}
@Override
public URI getAssertionConsumerServiceUrl() {
return assertionConsumerServiceUrl;
}
public void setAssertionConsumerServiceUrl(URI assertionConsumerServiceUrl) {
this.assertionConsumerServiceUrl = assertionConsumerServiceUrl;
}
public void setSignRequest(boolean signRequest) {
this.signRequest = signRequest;
}
@ -277,7 +288,6 @@ public class DefaultSamlDeployment implements SamlDeployment {
private boolean turnOffChangeSessionIdOnLogin;
private PrivateKey decryptionKey;
private KeyPair signingKeyPair;
private String assertionConsumerServiceUrl;
private Set<String> roleAttributeNames;
private PrincipalNamePolicy principalNamePolicy = PrincipalNamePolicy.FROM_NAME_ID;
private String principalAttributeName;
@ -340,11 +350,6 @@ public class DefaultSamlDeployment implements SamlDeployment {
return signingKeyPair;
}
@Override
public String getAssertionConsumerServiceUrl() {
return assertionConsumerServiceUrl;
}
@Override
public Set<String> getRoleAttributeNames() {
return roleAttributeNames;
@ -396,10 +401,6 @@ public class DefaultSamlDeployment implements SamlDeployment {
this.signingKeyPair = signingKeyPair;
}
public void setAssertionConsumerServiceUrl(String assertionConsumerServiceUrl) {
this.assertionConsumerServiceUrl = assertionConsumerServiceUrl;
}
public void setRoleAttributeNames(Set<String> roleAttributeNames) {
this.roleAttributeNames = roleAttributeNames;
}

View file

@ -25,6 +25,7 @@ import java.security.PrivateKey;
import java.util.Set;
import org.apache.http.client.HttpClient;
import org.keycloak.rotation.KeyLocator;
import java.net.URI;
/**
* Represents SAML deployment configuration.
@ -103,8 +104,24 @@ public interface SamlDeployment {
*/
boolean validateAssertionSignature();
Binding getRequestBinding();
/**
* SAML allows the client to request what binding type it wants authn responses to use. The default is
* that the client will not request a specific binding type for responses.
* @return
*/
Binding getResponseBinding();
/**
* Returns URL for the IDP login service that the client will send requests to.
* @return
*/
String getRequestBindingUrl();
/**
* Returns URI where the IdP should send the responses to. The default is
* that the client will not request a specific assertion consumer service URL.
* This property is typically accompanied by the ProtocolBinding attribute.
* @return
*/
URI getAssertionConsumerServiceUrl();
}
public interface SingleLogoutService {
@ -141,7 +158,6 @@ public interface SamlDeployment {
KeyPair getSigningKeyPair();
String getSignatureCanonicalizationMethod();
SignatureAlgorithm getSignatureAlgorithm();
String getAssertionConsumerServiceUrl();
String getLogoutPage();
Set<String> getRoleAttributeNames();

View file

@ -32,6 +32,7 @@ public class IDP implements Serializable {
private String requestBinding;
private String responseBinding;
private String bindingUrl;
private String assertionConsumerServiceUrl;
private boolean validateAssertionSignature;
public boolean isSignRequest() {
@ -81,6 +82,14 @@ public class IDP implements Serializable {
public void setBindingUrl(String bindingUrl) {
this.bindingUrl = bindingUrl;
}
public String getAssertionConsumerServiceUrl() {
return assertionConsumerServiceUrl;
}
public void setAssertionConsumerServiceUrl(String assertionConsumerServiceUrl) {
this.assertionConsumerServiceUrl = assertionConsumerServiceUrl;
}
}
public static class SingleLogoutService implements Serializable {

View file

@ -72,6 +72,7 @@ public class ConfigXmlConstants {
public static final String VALIDATE_REQUEST_SIGNATURE_ATTR = "validateRequestSignature";
public static final String POST_BINDING_URL_ATTR = "postBindingUrl";
public static final String REDIRECT_BINDING_URL_ATTR = "redirectBindingUrl";
public static final String ASSERTION_CONSUMER_SERVICE_URL_ATTR = "assertionConsumerServiceUrl";
public static final String HTTP_CLIENT_ELEMENT = "HttpClient";
public static final String ALLOW_ANY_HOSTNAME_ATTR = "allowAnyHostname";

View file

@ -41,6 +41,7 @@ import java.security.cert.Certificate;
import java.util.HashSet;
import java.util.Set;
import org.keycloak.adapters.cloned.HttpClientBuilder;
import java.net.URI;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@ -156,6 +157,12 @@ public class DeploymentBuilder {
if (sp.getIdp().getSingleSignOnService().getResponseBinding() != null) {
sso.setResponseBinding(SamlDeployment.Binding.parseBinding(sp.getIdp().getSingleSignOnService().getResponseBinding()));
}
if (sp.getIdp().getSingleSignOnService().getAssertionConsumerServiceUrl() != null) {
if (! sp.getIdp().getSingleSignOnService().getAssertionConsumerServiceUrl().endsWith("/saml")) {
throw new RuntimeException("AssertionConsumerServiceUrl must end with \"/saml\".");
}
sso.setAssertionConsumerServiceUrl(URI.create(sp.getIdp().getSingleSignOnService().getAssertionConsumerServiceUrl()));
}
sso.setSignRequest(sp.getIdp().getSingleSignOnService().isSignRequest());
sso.setValidateResponseSignature(sp.getIdp().getSingleSignOnService().isValidateResponseSignature());
sso.setValidateAssertionSignature(sp.getIdp().getSingleSignOnService().isValidateAssertionSignature());

View file

@ -118,6 +118,7 @@ public class IDPXmlParser extends AbstractParser {
sso.setRequestBinding(getAttributeValue(element, ConfigXmlConstants.REQUEST_BINDING_ATTR));
sso.setResponseBinding(getAttributeValue(element, ConfigXmlConstants.RESPONSE_BINDING_ATTR));
sso.setBindingUrl(getAttributeValue(element, ConfigXmlConstants.BINDING_URL_ATTR));
sso.setAssertionConsumerServiceUrl(getAttributeValue(element, ConfigXmlConstants.ASSERTION_CONSUMER_SERVICE_URL_ATTR));
return sso;
}

View file

@ -156,7 +156,15 @@ public class EcpAuthenticationHandler extends AbstractSamlAuthenticationHandler
paosRequestHeader.setMustUnderstand(true);
paosRequestHeader.setActor("http://schemas.xmlsoap.org/soap/actor/next");
paosRequestHeader.addAttribute(envelope.createName("service"), JBossSAMLURIConstants.ECP_PROFILE.get());
paosRequestHeader.addAttribute(envelope.createName("responseConsumerURL"), deployment.getAssertionConsumerServiceUrl());
paosRequestHeader.addAttribute(envelope.createName("responseConsumerURL"), getResponseConsumerUrl());
}
private String getResponseConsumerUrl() {
return (deployment.getIDP() == null
|| deployment.getIDP().getSingleSignOnService() == null
|| deployment.getIDP().getSingleSignOnService().getAssertionConsumerServiceUrl() == null
) ? null
: deployment.getIDP().getSingleSignOnService().getAssertionConsumerServiceUrl().toString();
}
};
}

View file

@ -0,0 +1,456 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ 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.
-->
<xs:schema version="1.0"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns="urn:keycloak:saml:adapter"
targetNamespace="urn:keycloak:saml:adapter"
elementFormDefault="qualified"
attributeFormDefault="unqualified">
<xs:element name="keycloak-saml-adapter" type="adapter-type"/>
<xs:complexType name="adapter-type">
<xs:annotation>
<xs:documentation>Keycloak SAML Adapter configuration file.</xs:documentation>
</xs:annotation>
<xs:all>
<xs:element name="SP" maxOccurs="1" minOccurs="0" type="sp-type">
<xs:annotation>
<xs:documentation>Describes SAML service provider configuration.</xs:documentation>
</xs:annotation>
</xs:element>
</xs:all>
</xs:complexType>
<xs:complexType name="sp-type">
<xs:all>
<xs:element name="Keys" type="keys-type" minOccurs="0" maxOccurs="1">
<xs:annotation>
<xs:documentation>
List of service provider encryption and validation keys.
If the IDP requires that the client application (SP) sign all of its requests and/or if the IDP will encrypt assertions, you must define the keys used to do this. For client signed documents you must define both the private and public key or certificate that will be used to sign documents. For encryption, you only have to define the private key that will be used to decrypt.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="PrincipalNameMapping" type="principal-name-mapping-type" minOccurs="0" maxOccurs="1">
<xs:annotation>
<xs:documentation>When creating a Java Principal object that you obtain from methods like HttpServletRequest.getUserPrincipal(), you can define what name that is returned by the Principal.getName() method.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="RoleIdentifiers" type="role-identifiers-type" minOccurs="0" maxOccurs="1">
<xs:annotation>
<xs:documentation>Defines what SAML attributes within the assertion received from the user should be used as role identifiers within the Java EE Security Context for the user.
By default Role attribute values are converted to Java EE roles. Some IDPs send roles via a member or memberOf attribute assertion. You can define one or more Attribute elements to specify which SAML attributes must be converted into roles.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="IDP" type="idp-type" minOccurs="1" maxOccurs="1">
<xs:annotation>
<xs:documentation>Describes configuration of SAML identity provider for this service provider.</xs:documentation>
</xs:annotation>
</xs:element>
</xs:all>
<xs:attribute name="entityID" type="xs:string" use="required">
<xs:annotation>
<xs:documentation>This is the identifier for this client. The IDP needs this value to determine who the client is that is communicating with it.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="sslPolicy" type="ssl-policy-type" use="optional">
<xs:annotation>
<xs:documentation>SSL policy the adapter will enforce.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="nameIDPolicyFormat" type="xs:string" use="optional">
<xs:annotation>
<xs:documentation>SAML clients can request a specific NameID Subject format. Fill in this value if you want a specific format. It must be a standard SAML format identifier, i.e. urn:oasis:names:tc:SAML:2.0:nameid-format:transient. By default, no special format is requested.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="logoutPage" type="xs:string" use="optional">
<xs:annotation>
<xs:documentation>URL of the logout page.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="forceAuthentication" type="xs:boolean" use="optional">
<xs:annotation>
<xs:documentation>SAML clients can request that a user is re-authenticated even if they are already logged in at the IDP. Default value is false.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="isPassive" type="xs:boolean" use="optional">
<xs:annotation>
<xs:documentation>SAML clients can request that a user is never asked to authenticate even if they are not logged in at the IDP. Set this to true if you want this. Do not use together with forceAuthentication as they are opposite. Default value is false.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="turnOffChangeSessionIdOnLogin" type="xs:boolean" use="optional">
<xs:annotation>
<xs:documentation>The session id is changed by default on a successful login on some platforms to plug a security attack vector. Change this to true to disable this. It is recommended you do not turn it off. Default value is false.</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
<xs:complexType name="keys-type">
<xs:sequence>
<xs:element name="Key" type="key-type" minOccurs="1" maxOccurs="unbounded">
<xs:annotation>
<xs:documentation>Describes a single key used for signing or encryption.</xs:documentation>
</xs:annotation>
</xs:element>
</xs:sequence>
</xs:complexType>
<xs:complexType name="key-type">
<xs:all>
<xs:element name="KeyStore" maxOccurs="1" minOccurs="0" type="key-store-type">
<xs:annotation>
<xs:documentation>Java keystore to load keys and certificates from.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="PrivateKeyPem" type="xs:string" minOccurs="0" maxOccurs="1">
<xs:annotation>
<xs:documentation>Private key (PEM format)</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="PublicKeyPem" type="xs:string" minOccurs="0" maxOccurs="1">
<xs:annotation>
<xs:documentation>Public key (PEM format)</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="CertificatePem" type="xs:string" minOccurs="0" maxOccurs="1">
<xs:annotation>
<xs:documentation>Certificate key (PEM format)</xs:documentation>
</xs:annotation>
</xs:element>
</xs:all>
<xs:attribute name="signing" type="xs:boolean" use="optional">
<xs:annotation>
<xs:documentation>Flag defining whether the key should be used for signing.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="encryption" type="xs:boolean" use="optional">
<xs:annotation>
<xs:documentation>Flag defining whether the key should be used for encryption</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
<xs:complexType name="key-store-type">
<xs:all>
<xs:element name="PrivateKey" maxOccurs="1" minOccurs="0" type="private-key-type">
<xs:annotation>
<xs:documentation>Private key declaration</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="Certificate" type="certificate-type" minOccurs="0" maxOccurs="1">
<xs:annotation>
<xs:documentation>Certificate declaration</xs:documentation>
</xs:annotation>
</xs:element>
</xs:all>
<xs:attribute name="file" type="xs:string" use="optional">
<xs:annotation>
<xs:documentation>File path to the key store.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="resource" type="xs:string" use="optional">
<xs:annotation>
<xs:documentation>WAR resource path to the key store. This is a path used in method call to ServletContext.getResourceAsStream().</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="password" type="xs:string" use="required">
<xs:annotation>
<xs:documentation>The password of the key store.</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
<xs:complexType name="private-key-type">
<xs:attribute name="alias" type="xs:string" use="required">
<xs:annotation>
<xs:documentation>Alias that points to the key or cert within the keystore.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="password" type="xs:string" use="required">
<xs:annotation>
<xs:documentation>Keystores require an additional password to access private keys. In the PrivateKey element you must define this password within a password attribute.</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
<xs:complexType name="certificate-type">
<xs:attribute name="alias" type="xs:string" use="required">
<xs:annotation>
<xs:documentation>Alias that points to the key or cert within the keystore.</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
<xs:complexType name="principal-name-mapping-type">
<xs:attribute name="policy" type="principal-name-mapping-policy-type" use="required">
<xs:annotation>
<xs:documentation>Policy used to populate value of Java Principal object obtained from methods like HttpServletRequest.getUserPrincipal().</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="attribute" type="xs:string" use="optional">
<xs:annotation>
<xs:documentation>Name of the SAML assertion attribute to use within.</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
<xs:simpleType name="principal-name-mapping-policy-type">
<xs:restriction base="xs:string">
<xs:enumeration value="FROM_NAME_ID">
<xs:annotation>
<xs:documentation>This policy just uses whatever the SAML subject value is. This is the default setting</xs:documentation>
</xs:annotation>
</xs:enumeration>
<xs:enumeration value="FROM_ATTRIBUTE">
<xs:annotation>
<xs:documentation>This will pull the value from one of the attributes declared in the SAML assertion received from the server. You'll need to specify the name of the SAML assertion attribute to use within the attribute XML attribute.</xs:documentation>
</xs:annotation>
</xs:enumeration>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="ssl-policy-type">
<xs:restriction base="xs:string">
<xs:enumeration value="ALL">
<xs:annotation>
<xs:documentation>All requests must come in via HTTPS.</xs:documentation>
</xs:annotation>
</xs:enumeration>
<xs:enumeration value="EXTERNAL">
<xs:annotation>
<xs:documentation>Only non-private IP addresses must come over the wire via HTTPS.</xs:documentation>
</xs:annotation>
</xs:enumeration>
<xs:enumeration value="NONE">
<xs:annotation>
<xs:documentation>no requests are required to come over via HTTPS.</xs:documentation>
</xs:annotation>
</xs:enumeration>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="signature-algorithm-type">
<xs:restriction base="xs:string">
<xs:enumeration value="RSA_SHA1"/>
<xs:enumeration value="RSA_SHA256"/>
<xs:enumeration value="RSA_SHA512"/>
<xs:enumeration value="DSA_SHA1"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="binding-type">
<xs:restriction base="xs:string">
<xs:enumeration value="POST"/>
<xs:enumeration value="REDIRECT"/>
</xs:restriction>
</xs:simpleType>
<xs:complexType name="role-identifiers-type">
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="Attribute" maxOccurs="unbounded" minOccurs="0" type="attribute-type">
<xs:annotation>
<xs:documentation>Specifies SAML attribute to be converted into roles.</xs:documentation>
</xs:annotation>
</xs:element>
</xs:choice>
</xs:complexType>
<xs:complexType name="attribute-type">
<xs:attribute name="name" type="xs:string" use="required">
<xs:annotation>
<xs:documentation>Specifies name of the SAML attribute to be converted into roles.</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
<xs:complexType name="idp-type">
<xs:sequence minOccurs="0" maxOccurs="unbounded">
<xs:element name="SingleSignOnService" maxOccurs="1" minOccurs="1" type="sign-on-type">
<xs:annotation>
<xs:documentation>Configuration of the login SAML endpoint of the IDP.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="SingleLogoutService" type="logout-type" minOccurs="0" maxOccurs="1">
<xs:annotation>
<xs:documentation>Configuration of the logout SAML endpoint of the IDP</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="Keys" type="keys-type" minOccurs="0" maxOccurs="1">
<xs:annotation>
<xs:documentation>The Keys sub element of IDP is only used to define the certificate or public key to use to verify documents signed by the IDP.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="HttpClient" type="http-client-type" minOccurs="0" maxOccurs="1">
<xs:annotation>
<xs:documentation>Configuration of HTTP client used for automatic obtaining of certificates containing public keys for IDP signature verification via SAML descriptor of the IDP.</xs:documentation>
</xs:annotation>
</xs:element>
</xs:sequence>
<xs:attribute name="entityID" type="xs:string" use="required">
<xs:annotation>
<xs:documentation>issuer ID of the IDP.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="signaturesRequired" type="xs:boolean" use="optional">
<xs:annotation>
<xs:documentation>If set to true, the client adapter will sign every document it sends to the IDP. Also, the client will expect that the IDP will be signing any documents sent to it. This switch sets the default for all request and response types.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="signatureAlgorithm" type="signature-algorithm-type" use="optional">
<xs:annotation>
<xs:documentation>Signature algorithm that the IDP expects signed documents to use. Defaults to RSA_SHA256</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="signatureCanonicalizationMethod" type="xs:string" use="optional">
<xs:annotation>
<xs:documentation>This is the signature canonicalization method that the IDP expects signed documents to use. The default value is https://www.w3.org/2001/10/xml-exc-c14n# and should be good for most IDPs.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="encryption" type="xs:boolean" use="optional">
<xs:annotation>
<xs:documentation></xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
<xs:complexType name="sign-on-type">
<xs:attribute name="signRequest" type="xs:boolean" use="optional">
<xs:annotation>
<xs:documentation>Should the client sign authn requests? Defaults to whatever the IDP signaturesRequired element value is.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="validateResponseSignature" type="xs:boolean" use="optional">
<xs:annotation>
<xs:documentation>Should the client expect the IDP to sign the assertion response document sent back from an auhtn request? Defaults to whatever the IDP signaturesRequired element value is.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="validateAssertionSignature" type="xs:boolean" use="optional">
<xs:annotation>
<xs:documentation>Should the client expect the IDP to sign the individual assertions sent back from an auhtn request? Defaults to whatever the IDP signaturesRequired element value is.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="requestBinding" type="binding-type" use="optional">
<xs:annotation>
<xs:documentation>SAML binding type used for communicating with the IDP. The default value is POST, but you can set it to REDIRECT as well.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="responseBinding" type="binding-type" use="optional">
<xs:annotation>
<xs:documentation>SAML allows the client to request what binding type it wants authn responses to use. This value maps to ProtocolBinding attribute in SAML AuthnRequest. The default is that the client will not request a specific binding type for responses.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="bindingUrl" type="xs:string" use="required">
<xs:annotation>
<xs:documentation>This is the URL for the IDP login service that the client will send requests to.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="assertionConsumerServiceUrl" type="xs:string" use="optional">
<xs:annotation>
<xs:documentation>URL of the assertion consumer service (ACS) where the IDP login service should send responses to. By default it is unset, relying on the IdP settings. When set, it must end in "/saml". This property is typically accompanied by the responseBinding attribute.</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
<xs:complexType name="logout-type">
<xs:attribute name="signRequest" type="xs:boolean" use="optional">
<xs:annotation>
<xs:documentation>Should the client sign authn requests? Defaults to whatever the IDP signaturesRequired element value is.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="signResponse" type="xs:boolean" use="optional">
<xs:annotation>
<xs:documentation>Should the client sign logout responses it sends to the IDP requests? Defaults to whatever the IDP signaturesRequired element value is.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="validateRequestSignature" type="xs:boolean" use="optional">
<xs:annotation>
<xs:documentation>Should the client expect signed logout request documents from the IDP? Defaults to whatever the IDP signaturesRequired element value is.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="validateResponseSignature" type="xs:boolean" use="optional">
<xs:annotation>
<xs:documentation>Should the client expect signed logout response documents from the IDP? Defaults to whatever the IDP signaturesRequired element value is.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="requestBinding" type="binding-type" use="optional">
<xs:annotation>
<xs:documentation>This is the SAML binding type used for communicating SAML requests to the IDP. The default value is POST.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="responseBinding" type="binding-type" use="optional">
<xs:annotation>
<xs:documentation>This is the SAML binding type used for communicating SAML responses to the IDP. The default value is POST.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="postBindingUrl" type="xs:string" use="optional">
<xs:annotation>
<xs:documentation>This is the URL for the IDP's logout service when using the POST binding. This setting is REQUIRED if using the POST binding.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="redirectBindingUrl" type="xs:string" use="optional">
<xs:annotation>
<xs:documentation>This is the URL for the IDP's logout service when using the REDIRECT binding. This setting is REQUIRED if using the REDIRECT binding.</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
<xs:complexType name="http-client-type">
<xs:attribute name="allowAnyHostname" type="xs:boolean" use="optional" default="false">
<xs:annotation>
<xs:documentation>If the the IDP server requires HTTPS and this config option is set to true the IDP's certificate
is validated via the truststore, but host name validation is not done. This setting should only be used during
development and never in production as it will partly disable verification of SSL certificates.
This seting may be useful in test environments. The default value is false.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="clientKeystore" type="xs:string" use="optional">
<xs:annotation>
<xs:documentation>This is the file path to a keystore file. This keystore contains client certificate
for two-way SSL when the adapter makes HTTPS requests to the IDP server.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="clientKeystorePassword" type="xs:string" use="optional">
<xs:annotation>
<xs:documentation>Password for the client keystore and for the client's key.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="connectionPoolSize" type="xs:int" use="optional" default="10">
<xs:annotation>
<xs:documentation>Defines number of pooled connections.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="disableTrustManager" type="xs:boolean" use="optional" default="false">
<xs:annotation>
<xs:documentation>If the the IDP server requires HTTPS and this config option is set to true you do not have to specify a truststore.
This setting should only be used during development and never in production as it will disable verification of SSL certificates.
The default value is false.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="proxyUrl" type="xs:string" use="optional">
<xs:annotation>
<xs:documentation>URL to HTTP proxy to use for HTTP connections.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="truststore" type="xs:string" use="optional">
<xs:annotation>
<xs:documentation>The value is the file path to a keystore file. If you prefix the path with classpath:,
then the truststore will be obtained from the deployment's classpath instead. Used for outgoing
HTTPS communications to the IDP server. Client making HTTPS requests need
a way to verify the host of the server they are talking to. This is what the trustore does.
The keystore contains one or more trusted host certificates or certificate authorities.
You can create this truststore by extracting the public certificate of the IDP's SSL keystore.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="truststorePassword" type="xs:string" use="optional">
<xs:annotation>
<xs:documentation>Password for the truststore keystore.</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:schema>

View file

@ -53,13 +53,17 @@ public class AuthzClient {
}
public static AuthzClient create(Configuration configuration) {
return new AuthzClient(configuration);
return new AuthzClient(configuration, configuration.getClientAuthenticator());
}
public static AuthzClient create(Configuration configuration, ClientAuthenticator authenticator) {
return new AuthzClient(configuration, authenticator);
}
private final ServerConfiguration serverConfiguration;
private final Configuration deployment;
private AuthzClient(Configuration configuration) {
private AuthzClient(Configuration configuration, ClientAuthenticator authenticator) {
if (configuration == null) {
throw new IllegalArgumentException("Client configuration can not be null.");
}
@ -72,7 +76,9 @@ public class AuthzClient {
configurationUrl += "/realms/" + configuration.getRealm() + "/.well-known/uma-configuration";
this.http = new Http(configuration);
this.deployment = configuration;
this.http = new Http(configuration, authenticator != null ? authenticator : configuration.getClientAuthenticator());
try {
this.serverConfiguration = this.http.<ServerConfiguration>get(URI.create(configurationUrl))
@ -83,8 +89,10 @@ public class AuthzClient {
}
this.http.setServerConfiguration(this.serverConfiguration);
}
this.deployment = configuration;
private AuthzClient(Configuration configuration) {
this(configuration, null);
}
public ProtectionResource protection() {
@ -106,7 +114,7 @@ public class AuthzClient {
public AccessTokenResponse obtainAccessToken() {
return this.http.<AccessTokenResponse>post(this.serverConfiguration.getTokenEndpoint())
.authentication()
.oauth2ClientCredentials()
.client()
.response()
.json(AccessTokenResponse.class)
.execute();

View file

@ -0,0 +1,45 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.authorization.client;
import java.util.Map;
import org.keycloak.AuthorizationContext;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig;
/**
*
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
public class ClientAuthorizationContext extends AuthorizationContext {
private final AuthzClient client;
public ClientAuthorizationContext(AccessToken authzToken, Map<String, PolicyEnforcerConfig.PathConfig> paths, AuthzClient client) {
super(authzToken, paths);
this.client = client;
}
public ClientAuthorizationContext(AuthzClient client) {
this.client = client;
}
public AuthzClient getClient() {
return client;
}
}

View file

@ -48,7 +48,7 @@ public class ProtectionResource {
public TokenIntrospectionResponse introspectRequestingPartyToken(String rpt) {
return this.http.<TokenIntrospectionResponse>post("/protocol/openid-connect/token/introspect")
.authentication()
.oauth2ClientCredentials()
.client()
.form()
.param("token_type_hint", "requesting_party_token")
.param("token", rpt)

View file

@ -18,6 +18,7 @@
package org.keycloak.authorization.client.util;
import org.apache.http.client.methods.RequestBuilder;
import org.keycloak.authorization.client.ClientAuthenticator;
import org.keycloak.authorization.client.Configuration;
import org.keycloak.authorization.client.representation.ServerConfiguration;
@ -29,10 +30,12 @@ import java.net.URI;
public class Http {
private final Configuration configuration;
private final ClientAuthenticator authenticator;
private ServerConfiguration serverConfiguration;
public Http(Configuration configuration) {
public Http(Configuration configuration, ClientAuthenticator authenticator) {
this.configuration = configuration;
this.authenticator = authenticator;
}
public <R> HttpMethod<R> get(String path) {
@ -60,7 +63,7 @@ public class Http {
}
private <R> HttpMethod<R> method(RequestBuilder builder) {
return new HttpMethod(this.configuration, builder);
return new HttpMethod(this.configuration, authenticator, builder);
}
public void setServerConfiguration(ServerConfiguration serverConfiguration) {

View file

@ -17,6 +17,12 @@
*/
package org.keycloak.authorization.client.util;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
@ -27,33 +33,30 @@ import org.apache.http.client.methods.RequestBuilder;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.keycloak.authorization.client.ClientAuthenticator;
import org.keycloak.authorization.client.Configuration;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
public class HttpMethod<R> {
private final HttpClient httpClient;
private final ClientAuthenticator authenticator;
private final RequestBuilder builder;
protected final Configuration configuration;
protected final HashMap<String, String> headers;
protected final HashMap<String, String> params;
private HttpMethodResponse<R> response;
public HttpMethod(Configuration configuration, RequestBuilder builder) {
this(configuration, builder, new HashMap<String, String>(), new HashMap<String, String>());
public HttpMethod(Configuration configuration, ClientAuthenticator authenticator, RequestBuilder builder) {
this(configuration, authenticator, builder, new HashMap<String, String>(), new HashMap<String, String>());
}
public HttpMethod(Configuration configuration, RequestBuilder builder, HashMap<String, String> params, HashMap<String, String> headers) {
public HttpMethod(Configuration configuration, ClientAuthenticator authenticator, RequestBuilder builder, HashMap<String, String> params, HashMap<String, String> headers) {
this.configuration = configuration;
this.httpClient = configuration.getHttpClient();
this.authenticator = authenticator;
this.builder = builder;
this.params = params;
this.headers = headers;
@ -121,7 +124,7 @@ public class HttpMethod<R> {
}
public HttpMethodAuthenticator<R> authentication() {
return new HttpMethodAuthenticator<R>(this);
return new HttpMethodAuthenticator<R>(this, authenticator);
}
public HttpMethod<R> param(String name, String value) {
@ -136,7 +139,7 @@ public class HttpMethod<R> {
}
public HttpMethod<R> form() {
return new HttpMethod<R>(this.configuration, this.builder, this.params, this.headers) {
return new HttpMethod<R>(this.configuration, authenticator, this.builder, this.params, this.headers) {
@Override
protected void preExecute(RequestBuilder builder) {
if (params != null) {

View file

@ -18,6 +18,7 @@
package org.keycloak.authorization.client.util;
import org.keycloak.OAuth2Constants;
import org.keycloak.authorization.client.ClientAuthenticator;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
@ -25,26 +26,24 @@ import org.keycloak.OAuth2Constants;
public class HttpMethodAuthenticator<R> {
private final HttpMethod<R> method;
private final ClientAuthenticator authenticator;
public HttpMethodAuthenticator(HttpMethod<R> method) {
public HttpMethodAuthenticator(HttpMethod<R> method, ClientAuthenticator authenticator) {
this.method = method;
this.authenticator = authenticator;
}
public HttpMethod<R> oauth2ClientCredentials() {
public HttpMethod<R> client() {
this.method.params.put(OAuth2Constants.GRANT_TYPE, OAuth2Constants.CLIENT_CREDENTIALS);
configureClientCredentials();
authenticator.configureClientCredentials(this.method.params, this.method.headers);
return this.method;
}
public HttpMethod<R> oauth2ResourceOwnerPassword(String userName, String password) {
client();
this.method.params.put(OAuth2Constants.GRANT_TYPE, OAuth2Constants.PASSWORD);
this.method.params.put("username", userName);
this.method.params.put("password", password);
configureClientCredentials();
return this.method;
}
private void configureClientCredentials() {
this.method.configuration.getClientAuthenticator().configureClientCredentials(this.method.params, this.method.headers);
}
}

View file

@ -23,18 +23,17 @@ import java.util.List;
import org.keycloak.Config;
import org.keycloak.authorization.AuthorizationProvider;
import org.keycloak.authorization.model.Policy;
import org.keycloak.authorization.model.ResourceServer;
import org.keycloak.authorization.policy.provider.PolicyProvider;
import org.keycloak.authorization.policy.provider.PolicyProviderAdminService;
import org.keycloak.authorization.policy.provider.PolicyProviderFactory;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.representations.idm.authorization.AggregatePolicyRepresentation;
import org.keycloak.representations.idm.authorization.PolicyRepresentation;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
public class AggregatePolicyProviderFactory implements PolicyProviderFactory<PolicyRepresentation> {
public class AggregatePolicyProviderFactory implements PolicyProviderFactory<AggregatePolicyRepresentation> {
private AggregatePolicyProvider provider = new AggregatePolicyProvider();
@ -53,26 +52,36 @@ public class AggregatePolicyProviderFactory implements PolicyProviderFactory<Pol
return provider;
}
@Override
public PolicyProviderAdminService getAdminResource(ResourceServer resourceServer, AuthorizationProvider authorization) {
return null;
}
@Override
public PolicyProvider create(KeycloakSession session) {
return null;
}
@Override
public void onCreate(Policy policy, PolicyRepresentation representation, AuthorizationProvider authorization) {
public void onCreate(Policy policy, AggregatePolicyRepresentation representation, AuthorizationProvider authorization) {
verifyCircularReference(policy, new ArrayList<>());
}
@Override
public void onUpdate(Policy policy, PolicyRepresentation representation, AuthorizationProvider authorization) {
public void onUpdate(Policy policy, AggregatePolicyRepresentation representation, AuthorizationProvider authorization) {
verifyCircularReference(policy, new ArrayList<>());
}
@Override
public void onImport(Policy policy, PolicyRepresentation representation, AuthorizationProvider authorization) {
verifyCircularReference(policy, new ArrayList<>());
}
@Override
public AggregatePolicyRepresentation toRepresentation(Policy policy, AggregatePolicyRepresentation representation) {
return representation;
}
@Override
public Class<AggregatePolicyRepresentation> getRepresentationType() {
return AggregatePolicyRepresentation.class;
}
private void verifyCircularReference(Policy policy, List<String> ids) {
if (!policy.getType().equals("aggregate")) {
return;

View file

@ -1,6 +1,6 @@
package org.keycloak.authorization.policy.provider.client;
import static org.keycloak.authorization.policy.provider.client.ClientPolicyProviderFactory.getClients;
import java.util.function.Function;
import org.keycloak.authorization.AuthorizationProvider;
import org.keycloak.authorization.model.Policy;
@ -9,24 +9,29 @@ import org.keycloak.authorization.policy.evaluation.EvaluationContext;
import org.keycloak.authorization.policy.provider.PolicyProvider;
import org.keycloak.models.ClientModel;
import org.keycloak.models.RealmModel;
import org.keycloak.representations.idm.authorization.ClientPolicyRepresentation;
public class ClientPolicyProvider implements PolicyProvider {
private final Function<Policy, ClientPolicyRepresentation> representationFunction;
public ClientPolicyProvider(Function<Policy, ClientPolicyRepresentation> representationFunction) {
this.representationFunction = representationFunction;
}
@Override
public void evaluate(Evaluation evaluation) {
Policy policy = evaluation.getPolicy();
EvaluationContext context = evaluation.getContext();
String[] clients = getClients(policy);
ClientPolicyRepresentation representation = representationFunction.apply(evaluation.getPolicy());
AuthorizationProvider authorizationProvider = evaluation.getAuthorizationProvider();
RealmModel realm = authorizationProvider.getKeycloakSession().getContext().getRealm();
EvaluationContext context = evaluation.getContext();
if (clients.length > 0) {
for (String client : clients) {
ClientModel clientModel = realm.getClientById(client);
if (context.getAttributes().containsValue("kc.client.id", clientModel.getClientId())) {
evaluation.grant();
return;
}
for (String client : representation.getClients()) {
ClientModel clientModel = realm.getClientById(client);
if (context.getAttributes().containsValue("kc.client.id", clientModel.getClientId())) {
evaluation.grant();
return;
}
}
}

View file

@ -2,14 +2,17 @@ package org.keycloak.authorization.policy.provider.client;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.keycloak.Config;
import org.keycloak.authorization.AuthorizationProvider;
import org.keycloak.authorization.model.Policy;
import org.keycloak.authorization.model.ResourceServer;
import org.keycloak.authorization.policy.provider.PolicyProvider;
import org.keycloak.authorization.policy.provider.PolicyProviderAdminService;
import org.keycloak.authorization.policy.provider.PolicyProviderFactory;
import org.keycloak.authorization.store.PolicyStore;
import org.keycloak.authorization.store.ResourceServerStore;
@ -17,12 +20,15 @@ import org.keycloak.authorization.store.StoreFactory;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RealmModel.ClientRemovedEvent;
import org.keycloak.representations.idm.authorization.ClientPolicyRepresentation;
import org.keycloak.representations.idm.authorization.PolicyRepresentation;
import org.keycloak.util.JsonSerialization;
public class ClientPolicyProviderFactory implements PolicyProviderFactory {
public class ClientPolicyProviderFactory implements PolicyProviderFactory<ClientPolicyRepresentation> {
private ClientPolicyProvider provider = new ClientPolicyProvider();
private ClientPolicyProvider provider = new ClientPolicyProvider(policy -> toRepresentation(policy, new ClientPolicyRepresentation()));
@Override
public String getName() {
@ -40,8 +46,29 @@ public class ClientPolicyProviderFactory implements PolicyProviderFactory {
}
@Override
public PolicyProviderAdminService getAdminResource(ResourceServer resourceServer, AuthorizationProvider authorization) {
return null;
public ClientPolicyRepresentation toRepresentation(Policy policy, ClientPolicyRepresentation representation) {
representation.setClients(new HashSet<>(Arrays.asList(getClients(policy))));
return representation;
}
@Override
public Class<ClientPolicyRepresentation> getRepresentationType() {
return ClientPolicyRepresentation.class;
}
@Override
public void onCreate(Policy policy, ClientPolicyRepresentation representation, AuthorizationProvider authorization) {
updateClients(policy, representation.getClients(), authorization);
}
@Override
public void onUpdate(Policy policy, ClientPolicyRepresentation representation, AuthorizationProvider authorization) {
updateClients(policy, representation.getClients(), authorization);
}
@Override
public void onImport(Policy policy, PolicyRepresentation representation, AuthorizationProvider authorization) {
updateClients(policy, new HashSet<>(Arrays.asList(getClients(policy))), authorization);
}
@Override
@ -101,7 +128,41 @@ public class ClientPolicyProviderFactory implements PolicyProviderFactory {
return "client";
}
static String[] getClients(Policy policy) {
private void updateClients(Policy policy, Set<String> clients, AuthorizationProvider authorization) {
RealmModel realm = authorization.getKeycloakSession().getContext().getRealm();
if (clients == null || clients.isEmpty()) {
throw new RuntimeException("No client provided.");
}
Set<String> updatedClients = new HashSet<>();
for (String id : clients) {
ClientModel client = realm.getClientByClientId(id);
if (client == null) {
client = realm.getClientById(id);
}
if (client == null) {
throw new RuntimeException("Error while updating policy [" + policy.getName() + "]. Client [" + id + "] could not be found.");
}
updatedClients.add(client.getId());
}
try {
Map<String, String> config = policy.getConfig();
config.put("clients", JsonSerialization.writeValueAsString(updatedClients));
policy.setConfig(config);
} catch (IOException cause) {
throw new RuntimeException("Failed to serialize clients", cause);
}
}
private String[] getClients(Policy policy) {
String clients = policy.getConfig().get("clients");
if (clients != null) {

View file

@ -1,30 +1,27 @@
package org.keycloak.authorization.policy.provider.js;
import java.util.function.Supplier;
import java.util.Map;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import org.keycloak.Config;
import org.keycloak.authorization.AuthorizationProvider;
import org.keycloak.authorization.model.ResourceServer;
import org.keycloak.authorization.model.Policy;
import org.keycloak.authorization.policy.provider.PolicyProvider;
import org.keycloak.authorization.policy.provider.PolicyProviderAdminService;
import org.keycloak.authorization.policy.provider.PolicyProviderFactory;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.representations.idm.authorization.JSPolicyRepresentation;
import org.keycloak.representations.idm.authorization.PolicyRepresentation;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
public class JSPolicyProviderFactory implements PolicyProviderFactory {
public class JSPolicyProviderFactory implements PolicyProviderFactory<JSPolicyRepresentation> {
private JSPolicyProvider provider = new JSPolicyProvider(new Supplier<ScriptEngine>() {
@Override
public ScriptEngine get() {
return new ScriptEngineManager().getEngineByName("nashorn");
}
});
private static final String ENGINE = "nashorn";
private JSPolicyProvider provider = new JSPolicyProvider(() -> new ScriptEngineManager().getEngineByName(ENGINE));
@Override
public String getName() {
@ -42,13 +39,40 @@ public class JSPolicyProviderFactory implements PolicyProviderFactory {
}
@Override
public PolicyProviderAdminService getAdminResource(ResourceServer resourceServer, AuthorizationProvider authorization) {
public PolicyProvider create(KeycloakSession session) {
return null;
}
@Override
public PolicyProvider create(KeycloakSession session) {
return null;
public JSPolicyRepresentation toRepresentation(Policy policy, JSPolicyRepresentation representation) {
representation.setCode(policy.getConfig().get("code"));
return representation;
}
@Override
public Class<JSPolicyRepresentation> getRepresentationType() {
return JSPolicyRepresentation.class;
}
@Override
public void onCreate(Policy policy, JSPolicyRepresentation representation, AuthorizationProvider authorization) {
updatePolicy(policy, representation.getCode());
}
@Override
public void onUpdate(Policy policy, JSPolicyRepresentation representation, AuthorizationProvider authorization) {
updatePolicy(policy, representation.getCode());
}
@Override
public void onImport(Policy policy, PolicyRepresentation representation, AuthorizationProvider authorization) {
updatePolicy(policy, representation.getConfig().get("code"));
}
private void updatePolicy(Policy policy, String code) {
Map<String, String> config = policy.getConfig();
config.put("code", code);
policy.setConfig(config);
}
@Override

View file

@ -17,9 +17,9 @@
*/
package org.keycloak.authorization.policy.provider.role;
import static org.keycloak.authorization.policy.provider.role.RolePolicyProviderFactory.getRoles;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import org.keycloak.authorization.AuthorizationProvider;
import org.keycloak.authorization.identity.Identity;
@ -29,43 +29,43 @@ import org.keycloak.authorization.policy.provider.PolicyProvider;
import org.keycloak.models.ClientModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.representations.idm.authorization.RolePolicyRepresentation;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
public class RolePolicyProvider implements PolicyProvider {
private final Function<Policy, RolePolicyRepresentation> representationFunction;
public RolePolicyProvider(Function<Policy, RolePolicyRepresentation> representationFunction) {
this.representationFunction = representationFunction;
}
@Override
public void evaluate(Evaluation evaluation) {
Policy policy = evaluation.getPolicy();
Map<String, Object>[] roleIds = getRoles(policy);
Set<RolePolicyRepresentation.RoleDefinition> roleIds = representationFunction.apply(policy).getRoles();
AuthorizationProvider authorizationProvider = evaluation.getAuthorizationProvider();
RealmModel realm = authorizationProvider.getKeycloakSession().getContext().getRealm();
Identity identity = evaluation.getContext().getIdentity();
if (roleIds.length > 0) {
Identity identity = evaluation.getContext().getIdentity();
for (RolePolicyRepresentation.RoleDefinition roleDefinition : roleIds) {
RoleModel role = realm.getRoleById(roleDefinition.getId());
for (Map<String, Object> current : roleIds) {
RoleModel role = realm.getRoleById((String) current.get("id"));
if (role != null) {
boolean hasRole = hasRole(identity, role, realm);
if (role != null) {
boolean hasRole = hasRole(identity, role, realm);
if (!hasRole && Boolean.valueOf(isRequired(current))) {
evaluation.deny();
return;
} else if (hasRole) {
evaluation.grant();
}
if (!hasRole && roleDefinition.isRequired()) {
evaluation.deny();
return;
} else if (hasRole) {
evaluation.grant();
}
}
}
}
private boolean isRequired(Map<String, Object> current) {
return (boolean) current.getOrDefault("required", false);
}
private boolean hasRole(Identity identity, RoleModel role, RealmModel realm) {
String roleName = role.getName();
if (role.isClientRole()) {

View file

@ -23,7 +23,6 @@ import org.keycloak.authorization.AuthorizationProvider;
import org.keycloak.authorization.model.Policy;
import org.keycloak.authorization.model.ResourceServer;
import org.keycloak.authorization.policy.provider.PolicyProvider;
import org.keycloak.authorization.policy.provider.PolicyProviderAdminService;
import org.keycloak.authorization.policy.provider.PolicyProviderFactory;
import org.keycloak.authorization.store.PolicyStore;
import org.keycloak.authorization.store.ResourceServerStore;
@ -53,7 +52,7 @@ import java.util.Set;
*/
public class RolePolicyProviderFactory implements PolicyProviderFactory<RolePolicyRepresentation> {
private RolePolicyProvider provider = new RolePolicyProvider();
private RolePolicyProvider provider = new RolePolicyProvider(policy -> toRepresentation(policy, new RolePolicyRepresentation()));
@Override
public String getName() {
@ -70,20 +69,15 @@ public class RolePolicyProviderFactory implements PolicyProviderFactory<RolePoli
return provider;
}
@Override
public PolicyProviderAdminService getAdminResource(ResourceServer resourceServer, AuthorizationProvider authorization) {
return null;
}
@Override
public PolicyProvider create(KeycloakSession session) {
return new RolePolicyProvider();
return provider;
}
@Override
public RolePolicyRepresentation toRepresentation(Policy policy, RolePolicyRepresentation representation) {
try {
representation.setRoles(JsonSerialization.readValue(policy.getConfig().get("roles"), Set.class));
representation.setRoles(new HashSet<>(Arrays.asList(JsonSerialization.readValue(policy.getConfig().get("roles"), RolePolicyRepresentation.RoleDefinition[].class))));
} catch (IOException cause) {
throw new RuntimeException("Failed to deserialize roles", cause);
}
@ -119,65 +113,63 @@ public class RolePolicyProviderFactory implements PolicyProviderFactory<RolePoli
}
private void updateRoles(Policy policy, AuthorizationProvider authorization, Set<RolePolicyRepresentation.RoleDefinition> roles) {
try {
RealmModel realm = authorization.getRealm();
Set<RolePolicyRepresentation.RoleDefinition> updatedRoles = new HashSet<>();
RealmModel realm = authorization.getRealm();
Set<RolePolicyRepresentation.RoleDefinition> updatedRoles = new HashSet<>();
if (roles != null) {
for (RolePolicyRepresentation.RoleDefinition definition : roles) {
String roleName = definition.getId();
String clientId = null;
int clientIdSeparator = roleName.indexOf("/");
if (roles != null) {
for (RolePolicyRepresentation.RoleDefinition definition : roles) {
String roleName = definition.getId();
String clientId = null;
int clientIdSeparator = roleName.indexOf("/");
if (clientIdSeparator != -1) {
clientId = roleName.substring(0, clientIdSeparator);
roleName = roleName.substring(clientIdSeparator + 1);
}
if (clientIdSeparator != -1) {
clientId = roleName.substring(0, clientIdSeparator);
roleName = roleName.substring(clientIdSeparator + 1);
}
RoleModel role;
RoleModel role;
if (clientId == null) {
role = realm.getRole(roleName);
if (role == null) {
role = realm.getRoleById(roleName);
}
} else {
ClientModel client = realm.getClientByClientId(clientId);
if (client == null) {
throw new RuntimeException("Client with id [" + clientId + "] not found.");
}
role = client.getRole(roleName);
}
// fallback to find any client role with the given name
if (role == null) {
String finalRoleName = roleName;
role = realm.getClients().stream().map(clientModel -> clientModel.getRole(finalRoleName)).filter(roleModel -> roleModel != null)
.findFirst().orElse(null);
}
if (clientId == null) {
role = realm.getRole(roleName);
if (role == null) {
throw new RuntimeException("Error while importing configuration. Role [" + roleName + "] could not be found.");
role = realm.getRoleById(roleName);
}
} else {
ClientModel client = realm.getClientByClientId(clientId);
if (client == null) {
throw new RuntimeException("Client with id [" + clientId + "] not found.");
}
definition.setId(role.getId());
role = client.getRole(roleName);
}
updatedRoles.add(definition);
// fallback to find any client role with the given name
if (role == null) {
String finalRoleName = roleName;
role = realm.getClients().stream().map(clientModel -> clientModel.getRole(finalRoleName)).filter(roleModel -> roleModel != null)
.findFirst().orElse(null);
}
try {
} catch (Exception e) {
throw new RuntimeException("Error while updating policy [" + policy.getName() + "].", e);
if (role == null) {
throw new RuntimeException("Error while updating policy [" + policy.getName() + "]. Role [" + roleName + "] could not be found.");
}
definition.setId(role.getId());
updatedRoles.add(definition);
}
}
try {
Map<String, String> config = policy.getConfig();
config.put("roles", JsonSerialization.writeValueAsString(updatedRoles));
policy.setConfig(config);
} catch (IOException cause) {
throw new RuntimeException("Failed to deserialize roles", cause);
throw new RuntimeException("Failed to serialize roles", cause);
}
}
@ -253,7 +245,7 @@ public class RolePolicyProviderFactory implements PolicyProviderFactory<RolePoli
return "role";
}
static Map<String, Object>[] getRoles(Policy policy) {
private Map<String, Object>[] getRoles(Policy policy) {
String roles = policy.getConfig().get("roles");
if (roles != null) {

View file

@ -33,21 +33,20 @@ public class TimePolicyProvider implements PolicyProvider {
static String DEFAULT_DATE_PATTERN = "yyyy-MM-dd hh:mm:ss";
private final SimpleDateFormat dateFormat;
private final Date currentDate;
public TimePolicyProvider() {
this.dateFormat = new SimpleDateFormat(DEFAULT_DATE_PATTERN);
this.currentDate = new Date();
}
@Override
public void evaluate(Evaluation evaluation) {
Policy policy = evaluation.getPolicy();
Date actualDate = new Date();
try {
String notBefore = policy.getConfig().get("nbf");
if (notBefore != null && !"".equals(notBefore)) {
if (this.currentDate.before(this.dateFormat.parse(format(notBefore)))) {
if (actualDate.before(this.dateFormat.parse(format(notBefore)))) {
evaluation.deny();
return;
}
@ -55,17 +54,17 @@ public class TimePolicyProvider implements PolicyProvider {
String notOnOrAfter = policy.getConfig().get("noa");
if (notOnOrAfter != null && !"".equals(notOnOrAfter)) {
if (this.currentDate.after(this.dateFormat.parse(format(notOnOrAfter)))) {
if (actualDate.after(this.dateFormat.parse(format(notOnOrAfter)))) {
evaluation.deny();
return;
}
}
if (isInvalid(Calendar.DAY_OF_MONTH, "dayMonth", policy)
|| isInvalid(Calendar.MONTH, "month", policy)
|| isInvalid(Calendar.YEAR, "year", policy)
|| isInvalid(Calendar.HOUR_OF_DAY, "hour", policy)
|| isInvalid(Calendar.MINUTE, "minute", policy)) {
if (isInvalid(actualDate, Calendar.DAY_OF_MONTH, "dayMonth", policy)
|| isInvalid(actualDate, Calendar.MONTH, "month", policy)
|| isInvalid(actualDate, Calendar.YEAR, "year", policy)
|| isInvalid(actualDate, Calendar.HOUR_OF_DAY, "hour", policy)
|| isInvalid(actualDate, Calendar.MINUTE, "minute", policy)) {
evaluation.deny();
return;
}
@ -76,10 +75,10 @@ public class TimePolicyProvider implements PolicyProvider {
}
}
private boolean isInvalid(int timeConstant, String configName, Policy policy) {
private boolean isInvalid(Date actualDate, int timeConstant, String configName, Policy policy) {
Calendar calendar = Calendar.getInstance();
calendar.setTime(this.currentDate);
calendar.setTime(actualDate);
int dateField = calendar.get(timeConstant);

View file

@ -1,22 +1,22 @@
package org.keycloak.authorization.policy.provider.time;
import java.text.SimpleDateFormat;
import java.util.Map;
import org.keycloak.Config;
import org.keycloak.authorization.AuthorizationProvider;
import org.keycloak.authorization.model.Policy;
import org.keycloak.authorization.model.ResourceServer;
import org.keycloak.authorization.policy.provider.PolicyProvider;
import org.keycloak.authorization.policy.provider.PolicyProviderAdminService;
import org.keycloak.authorization.policy.provider.PolicyProviderFactory;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.representations.idm.authorization.PolicyRepresentation;
import org.keycloak.representations.idm.authorization.TimePolicyRepresentation;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
public class TimePolicyProviderFactory implements PolicyProviderFactory<PolicyRepresentation> {
public class TimePolicyProviderFactory implements PolicyProviderFactory<TimePolicyRepresentation> {
private TimePolicyProvider provider = new TimePolicyProvider();
@ -35,46 +35,58 @@ public class TimePolicyProviderFactory implements PolicyProviderFactory<PolicyRe
return provider;
}
@Override
public PolicyProviderAdminService getAdminResource(ResourceServer resourceServer, AuthorizationProvider authorization) {
return new TimePolicyAdminResource();
}
@Override
public PolicyProvider create(KeycloakSession session) {
return null;
}
@Override
public void onCreate(Policy policy, PolicyRepresentation representation, AuthorizationProvider authorization) {
validateConfig(policy);
}
private void validateConfig(Policy policy) {
String nbf = policy.getConfig().get("nbf");
String noa = policy.getConfig().get("noa");
if (nbf != null && noa != null) {
validateFormat(nbf);
validateFormat(noa);
}
public void onCreate(Policy policy, TimePolicyRepresentation representation, AuthorizationProvider authorization) {
updatePolicy(policy, representation);
}
@Override
public void onUpdate(Policy policy, PolicyRepresentation representation, AuthorizationProvider authorization) {
validateConfig(policy);
public void onUpdate(Policy policy, TimePolicyRepresentation representation, AuthorizationProvider authorization) {
updatePolicy(policy, representation);
}
@Override
public void onRemove(Policy policy, AuthorizationProvider authorization) {
}
private void validateFormat(String date) {
try {
new SimpleDateFormat(TimePolicyProvider.DEFAULT_DATE_PATTERN).parse(TimePolicyProvider.format(date));
} catch (Exception e) {
throw new RuntimeException("Could not parse a date using format [" + date + "]");
}
@Override
public void onImport(Policy policy, PolicyRepresentation representation, AuthorizationProvider authorization) {
policy.setConfig(representation.getConfig());
}
@Override
public Class<TimePolicyRepresentation> getRepresentationType() {
return TimePolicyRepresentation.class;
}
@Override
public TimePolicyRepresentation toRepresentation(Policy policy, TimePolicyRepresentation representation) {
Map<String, String> config = policy.getConfig();
representation.setDayMonth(config.get("dayMonth"));
representation.setDayMonthEnd(config.get("dayMonthEnd"));
representation.setMonth(config.get("month"));
representation.setMonthEnd(config.get("monthEnd"));
representation.setYear(config.get("year"));
representation.setYearEnd(config.get("yearEnd"));
representation.setHour(config.get("hour"));
representation.setHourEnd(config.get("hourEnd"));
representation.setMinute(config.get("minute"));
representation.setMinuteEnd(config.get("minuteEnd"));
representation.setNotBefore(config.get("nbf"));
representation.setNotOnOrAfter(config.get("noa"));
return representation;
}
@Override
@ -96,4 +108,44 @@ public class TimePolicyProviderFactory implements PolicyProviderFactory<PolicyRe
public String getId() {
return "time";
}
private void updatePolicy(Policy policy, TimePolicyRepresentation representation) {
String nbf = representation.getNotBefore();
String noa = representation.getNotOnOrAfter();
if (nbf != null && noa != null) {
validateFormat(nbf);
validateFormat(noa);
}
Map<String, String> config = policy.getConfig();
config.compute("nbf", (s, s2) -> nbf != null ? nbf : null);
config.compute("noa", (s, s2) -> noa != null ? noa : null);
config.compute("dayMonth", (s, s2) -> representation.getDayMonth() != null ? representation.getDayMonth() : null);
config.compute("dayMonthEnd", (s, s2) -> representation.getDayMonthEnd() != null ? representation.getDayMonthEnd() : null);
config.compute("month", (s, s2) -> representation.getMonth() != null ? representation.getMonth() : null);
config.compute("monthEnd", (s, s2) -> representation.getMonthEnd() != null ? representation.getMonthEnd() : null);
config.compute("year", (s, s2) -> representation.getYear() != null ? representation.getYear() : null);
config.compute("yearEnd", (s, s2) -> representation.getYearEnd() != null ? representation.getYearEnd() : null);
config.compute("hour", (s, s2) -> representation.getHour() != null ? representation.getHour() : null);
config.compute("hourEnd", (s, s2) -> representation.getHourEnd() != null ? representation.getHourEnd() : null);
config.compute("minute", (s, s2) -> representation.getMinute() != null ? representation.getMinute() : null);
config.compute("minuteEnd", (s, s2) -> representation.getMinuteEnd() != null ? representation.getMinuteEnd() : null);
policy.setConfig(config);
}
private void validateFormat(String date) {
try {
new SimpleDateFormat(TimePolicyProvider.DEFAULT_DATE_PATTERN).parse(TimePolicyProvider.format(date));
} catch (Exception e) {
throw new RuntimeException("Could not parse a date using format [" + date + "]");
}
}
}

View file

@ -17,30 +17,34 @@
*/
package org.keycloak.authorization.policy.provider.user;
import java.util.function.Function;
import org.keycloak.authorization.model.Policy;
import org.keycloak.authorization.policy.evaluation.Evaluation;
import org.keycloak.authorization.policy.evaluation.EvaluationContext;
import org.keycloak.authorization.policy.provider.PolicyProvider;
import static org.keycloak.authorization.policy.provider.user.UserPolicyProviderFactory.getUsers;
import org.keycloak.representations.idm.authorization.UserPolicyRepresentation;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
public class UserPolicyProvider implements PolicyProvider {
private final Function<Policy, UserPolicyRepresentation> representationFunction;
public UserPolicyProvider(Function<Policy, UserPolicyRepresentation> representationFunction) {
this.representationFunction = representationFunction;
}
@Override
public void evaluate(Evaluation evaluation) {
Policy policy = evaluation.getPolicy();
EvaluationContext context = evaluation.getContext();
String[] userIds = getUsers(policy);
UserPolicyRepresentation representation = representationFunction.apply(evaluation.getPolicy());
if (userIds.length > 0) {
for (String userId : userIds) {
if (context.getIdentity().getId().equals(userId)) {
evaluation.grant();
break;
}
for (String userId : representation.getUsers()) {
if (context.getIdentity().getId().equals(userId)) {
evaluation.grant();
break;
}
}
}

View file

@ -24,6 +24,7 @@ import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import org.keycloak.Config;
import org.keycloak.authorization.AuthorizationProvider;
@ -49,7 +50,7 @@ import org.keycloak.util.JsonSerialization;
*/
public class UserPolicyProviderFactory implements PolicyProviderFactory<UserPolicyRepresentation> {
private UserPolicyProvider provider = new UserPolicyProvider();
private UserPolicyProvider provider = new UserPolicyProvider((Function<Policy, UserPolicyRepresentation>) policy -> toRepresentation(policy, new UserPolicyRepresentation()));
@Override
public String getName() {
@ -110,42 +111,40 @@ public class UserPolicyProviderFactory implements PolicyProviderFactory<UserPoli
}
private void updateUsers(Policy policy, AuthorizationProvider authorization, Set<String> users) {
try {
KeycloakSession session = authorization.getKeycloakSession();
RealmModel realm = authorization.getRealm();
UserProvider userProvider = session.users();
Set<String> updatedUsers = new HashSet<>();
KeycloakSession session = authorization.getKeycloakSession();
RealmModel realm = authorization.getRealm();
UserProvider userProvider = session.users();
Set<String> updatedUsers = new HashSet<>();
if (users != null) {
for (String userId : users) {
UserModel user = null;
if (users != null) {
try {
for (String userId : users) {
UserModel user = null;
try {
user = userProvider.getUserByUsername(userId, realm);
} catch (Exception ignore) {
}
if (user == null) {
user = userProvider.getUserById(userId, realm);
}
if (user == null) {
throw new RuntimeException("Error while importing configuration. User [" + userId + "] could not be found.");
}
updatedUsers.add(user.getId());
}
} catch (Exception e) {
throw new RuntimeException("Error while updating policy [" + policy.getName() + "].", e);
user = userProvider.getUserByUsername(userId, realm);
} catch (Exception ignore) {
}
}
if (user == null) {
user = userProvider.getUserById(userId, realm);
}
if (user == null) {
throw new RuntimeException("Error while updating policy [" + policy.getName() + "]. User [" + userId + "] could not be found.");
}
updatedUsers.add(user.getId());
}
}
try {
Map<String, String> config = policy.getConfig();
config.put("users", JsonSerialization.writeValueAsString(updatedUsers));
policy.setConfig(config);
} catch (IOException cause) {
throw new RuntimeException("Failed to deserialize roles", cause);
throw new RuntimeException("Failed to serialize users", cause);
}
}

View file

@ -17,13 +17,14 @@
package org.keycloak.authorization.policy.provider.drools;
import org.keycloak.authorization.policy.provider.PolicyProviderAdminService;
import org.keycloak.representations.idm.authorization.PolicyRepresentation;
import org.keycloak.representations.idm.authorization.RulePolicyRepresentation;
import org.kie.api.runtime.KieContainer;
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
/**
@ -39,24 +40,24 @@ public class DroolsPolicyAdminResource implements PolicyProviderAdminService {
@Path("/resolveModules")
@POST
@Consumes("application/json")
@Consumes(MediaType.APPLICATION_JSON)
@Produces("application/json")
public Response resolveModules(PolicyRepresentation policy) {
public Response resolveModules(RulePolicyRepresentation policy) {
return Response.ok(getContainer(policy).getKieBaseNames()).build();
}
@Path("/resolveSessions")
@POST
@Consumes("application/json")
@Produces("application/json")
public Response resolveSessions(PolicyRepresentation policy) {
return Response.ok(getContainer(policy).getKieSessionNamesInKieBase(policy.getConfig().get("moduleName"))).build();
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public Response resolveSessions(RulePolicyRepresentation policy) {
return Response.ok(getContainer(policy).getKieSessionNamesInKieBase(policy.getModuleName())).build();
}
private KieContainer getContainer(PolicyRepresentation policy) {
String groupId = policy.getConfig().get("mavenArtifactGroupId");
String artifactId = policy.getConfig().get("mavenArtifactId");
String version = policy.getConfig().get("mavenArtifactVersion");
private KieContainer getContainer(RulePolicyRepresentation policy) {
String groupId = policy.getArtifactGroupId();
String artifactId = policy.getArtifactId();
String version = policy.getArtifactVersion();
return this.factory.getKieContainer(groupId, artifactId, version);
}
}

View file

@ -13,8 +13,8 @@ import org.keycloak.authorization.policy.provider.PolicyProviderAdminService;
import org.keycloak.authorization.policy.provider.PolicyProviderFactory;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.representations.idm.authorization.AbstractPolicyRepresentation;
import org.keycloak.representations.idm.authorization.PolicyRepresentation;
import org.keycloak.representations.idm.authorization.RulePolicyRepresentation;
import org.kie.api.KieServices;
import org.kie.api.KieServices.Factory;
import org.kie.api.runtime.KieContainer;
@ -22,7 +22,7 @@ import org.kie.api.runtime.KieContainer;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
public class DroolsPolicyProviderFactory implements PolicyProviderFactory {
public class DroolsPolicyProviderFactory implements PolicyProviderFactory<RulePolicyRepresentation> {
private KieServices ks;
private final Map<String, DroolsPolicy> containers = Collections.synchronizedMap(new HashMap<>());
@ -61,12 +61,14 @@ public class DroolsPolicyProviderFactory implements PolicyProviderFactory {
}
@Override
public void onCreate(Policy policy, AbstractPolicyRepresentation representation, AuthorizationProvider authorization) {
public void onCreate(Policy policy, RulePolicyRepresentation representation, AuthorizationProvider authorization) {
updateConfig(policy, representation);
update(policy);
}
@Override
public void onUpdate(Policy policy, AbstractPolicyRepresentation representation, AuthorizationProvider authorization) {
public void onUpdate(Policy policy, RulePolicyRepresentation representation, AuthorizationProvider authorization) {
updateConfig(policy, representation);
update(policy);
}
@ -80,6 +82,23 @@ public class DroolsPolicyProviderFactory implements PolicyProviderFactory {
remove(policy);
}
@Override
public RulePolicyRepresentation toRepresentation(Policy policy, RulePolicyRepresentation representation) {
representation.setArtifactGroupId(policy.getConfig().get("mavenArtifactGroupId"));
representation.setArtifactId(policy.getConfig().get("mavenArtifactId"));
representation.setArtifactVersion(policy.getConfig().get("mavenArtifactVersion"));
representation.setScannerPeriod(policy.getConfig().get("scannerPeriod"));
representation.setScannerPeriodUnit(policy.getConfig().get("scannerPeriodUnit"));
representation.setSessionName(policy.getConfig().get("sessionName"));
representation.setModuleName(policy.getConfig().get("moduleName"));
return representation;
}
@Override
public Class<RulePolicyRepresentation> getRepresentationType() {
return RulePolicyRepresentation.class;
}
@Override
public void init(Config.Scope config) {
this.ks = Factory.get();
@ -100,6 +119,20 @@ public class DroolsPolicyProviderFactory implements PolicyProviderFactory {
return "rules";
}
private void updateConfig(Policy policy, RulePolicyRepresentation representation) {
Map<String, String> config = policy.getConfig();
config.put("mavenArtifactGroupId", representation.getArtifactGroupId());
config.put("mavenArtifactId", representation.getArtifactId());
config.put("mavenArtifactVersion", representation.getArtifactVersion());
config.put("scannerPeriod", representation.getScannerPeriod());
config.put("scannerPeriodUnit", representation.getScannerPeriodUnit());
config.put("sessionName", representation.getSessionName());
config.put("moduleName", representation.getModuleName());
policy.setConfig(config);
}
void update(Policy policy) {
remove(policy);
this.containers.put(policy.getId(), new DroolsPolicy(this.ks, policy));

View file

@ -40,8 +40,8 @@ public class KeyUtils {
private KeyUtils() {
}
public static SecretKey loadSecretKey(String secret) {
return new SecretKeySpec(secret.getBytes(), "HmacSHA256");
public static SecretKey loadSecretKey(byte[] secret) {
return new SecretKeySpec(secret, "HmacSHA256");
}
public static KeyPair generateRsaKeyPair(int keysize) {

View file

@ -0,0 +1,24 @@
package org.keycloak.common.util;
import org.junit.Test;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.util.concurrent.ThreadLocalRandom;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
public class KeyUtilsTest {
@Test
public void loadSecretKey() throws Exception {
byte[] secretBytes = new byte[32];
ThreadLocalRandom.current().nextBytes(secretBytes);
SecretKeySpec expected = new SecretKeySpec(secretBytes, "HmacSHA256");
SecretKey actual = KeyUtils.loadSecretKey(secretBytes);
assertEquals(expected.getAlgorithm(), actual.getAlgorithm());
assertArrayEquals(expected.getEncoded(), actual.getEncoded());
}
}

View file

@ -88,6 +88,10 @@ public class AbstractPolicyRepresentation {
return policies;
}
public void setPolicies(Set<String> policies) {
this.policies = policies;
}
public void addPolicy(String... id) {
if (this.policies == null) {
this.policies = new HashSet<>();
@ -99,6 +103,10 @@ public class AbstractPolicyRepresentation {
return resources;
}
public void setResources(Set<String> resources) {
this.resources = resources;
}
public void addResource(String id) {
if (this.resources == null) {
this.resources = new HashSet<>();
@ -110,6 +118,10 @@ public class AbstractPolicyRepresentation {
return scopes;
}
public void setScopes(Set<String> scopes) {
this.scopes = scopes;
}
public void addScope(String... id) {
if (this.scopes == null) {
this.scopes = new HashSet<>();

View file

@ -1,13 +1,12 @@
/*
* JBoss, Home of Professional Open Source.
* Copyright 2016 Red Hat, Inc., and individual contributors
* as indicated by the @author tags.
* 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
* 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,
@ -15,14 +14,15 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.authorization.policy.provider.time;
import org.keycloak.authorization.policy.provider.PolicyProviderAdminService;
package org.keycloak.representations.idm.authorization;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
public class TimePolicyAdminResource implements PolicyProviderAdminService {
public class AggregatePolicyRepresentation extends AbstractPolicyRepresentation {
@Override
public String getType() {
return "aggregate";
}
}

View file

@ -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.representations.idm.authorization;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
public class ClientPolicyRepresentation extends AbstractPolicyRepresentation {
private Set<String> clients;
@Override
public String getType() {
return "client";
}
public Set<String> getClients() {
return clients;
}
public void setClients(Set<String> clients) {
this.clients = clients;
}
public void addClient(String... id) {
if (this.clients == null) {
this.clients = new HashSet<>();
}
this.clients.addAll(Arrays.asList(id));
}
}

View file

@ -0,0 +1,38 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.representations.idm.authorization;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
public class JSPolicyRepresentation extends AbstractPolicyRepresentation {
private String code;
@Override
public String getType() {
return "js";
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
}

View file

@ -26,6 +26,11 @@ public class RolePolicyRepresentation extends AbstractPolicyRepresentation {
private Set<RoleDefinition> roles;
@Override
public String getType() {
return "role";
}
public Set<RoleDefinition> getRoles() {
return roles;
}

View file

@ -0,0 +1,92 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.representations.idm.authorization;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
public class RulePolicyRepresentation extends AbstractPolicyRepresentation {
private String artifactGroupId;
private String artifactId;
private String artifactVersion;
private String moduleName;
private String sessionName;
private String scannerPeriod;
private String scannerPeriodUnit;
@Override
public String getType() {
return "rules";
}
public String getArtifactGroupId() {
return artifactGroupId;
}
public void setArtifactGroupId(String artifactGroupId) {
this.artifactGroupId = artifactGroupId;
}
public String getArtifactId() {
return artifactId;
}
public void setArtifactId(String artifactId) {
this.artifactId = artifactId;
}
public String getArtifactVersion() {
return artifactVersion;
}
public void setArtifactVersion(String artifactVersion) {
this.artifactVersion = artifactVersion;
}
public String getModuleName() {
return moduleName;
}
public void setModuleName(String moduleName) {
this.moduleName = moduleName;
}
public String getSessionName() {
return sessionName;
}
public void setSessionName(String sessionName) {
this.sessionName = sessionName;
}
public String getScannerPeriod() {
return scannerPeriod;
}
public void setScannerPeriod(String scannerPeriod) {
this.scannerPeriod = scannerPeriod;
}
public String getScannerPeriodUnit() {
return scannerPeriodUnit;
}
public void setScannerPeriodUnit(String scannerPeriodUnit) {
this.scannerPeriodUnit = scannerPeriodUnit;
}
}

View file

@ -0,0 +1,137 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.representations.idm.authorization;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
public class TimePolicyRepresentation extends AbstractPolicyRepresentation {
private String notBefore;
private String notOnOrAfter;
private String dayMonth;
private String dayMonthEnd;
private String month;
private String monthEnd;
private String year;
private String yearEnd;
private String hour;
private String hourEnd;
private String minute;
private String minuteEnd;
@Override
public String getType() {
return "time";
}
public String getNotBefore() {
return notBefore;
}
public void setNotBefore(String notBefore) {
this.notBefore = notBefore;
}
public String getNotOnOrAfter() {
return notOnOrAfter;
}
public void setNotOnOrAfter(String notOnOrAfter) {
this.notOnOrAfter = notOnOrAfter;
}
public String getDayMonth() {
return dayMonth;
}
public void setDayMonth(String dayMonth) {
this.dayMonth = dayMonth;
}
public String getDayMonthEnd() {
return dayMonthEnd;
}
public void setDayMonthEnd(String dayMonthEnd) {
this.dayMonthEnd = dayMonthEnd;
}
public String getMonth() {
return month;
}
public void setMonth(String month) {
this.month = month;
}
public String getMonthEnd() {
return monthEnd;
}
public void setMonthEnd(String monthEnd) {
this.monthEnd = monthEnd;
}
public String getYear() {
return year;
}
public void setYear(String year) {
this.year = year;
}
public String getYearEnd() {
return yearEnd;
}
public void setYearEnd(String yearEnd) {
this.yearEnd = yearEnd;
}
public String getHour() {
return hour;
}
public void setHour(String hour) {
this.hour = hour;
}
public String getHourEnd() {
return hourEnd;
}
public void setHourEnd(String hourEnd) {
this.hourEnd = hourEnd;
}
public String getMinute() {
return minute;
}
public void setMinute(String minute) {
this.minute = minute;
}
public String getMinuteEnd() {
return minuteEnd;
}
public void setMinuteEnd(String minuteEnd) {
this.minuteEnd = minuteEnd;
}
}

View file

@ -26,6 +26,11 @@ public class UserPolicyRepresentation extends AbstractPolicyRepresentation {
private Set<String> users;
@Override
public String getType() {
return "user";
}
public Set<String> getUsers() {
return users;
}

View file

@ -15,71 +15,65 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<!-- See src/resources/configuration/ReadMe.txt for how the configuration assembly works -->
<config>
<subsystems name="load-balancer">
<!-- Each subsystem to be included relative to the src/main/resources directory -->
<subsystem>logging.xml</subsystem>
<subsystem>io.xml</subsystem>
<subsystem supplement="domain">jmx.xml</subsystem>
<subsystem>naming.xml</subsystem>
<subsystem>remoting.xml</subsystem>
<subsystem>request-controller.xml</subsystem>
<subsystem>security.xml</subsystem>
<subsystem>security-manager.xml</subsystem>
</subsystems>
<subsystems name="auth-server-standalone">
<!-- Each subsystem to be included relative to the src/main/resources directory -->
<subsystem>logging.xml</subsystem>
<subsystem>bean-validation.xml</subsystem>
<subsystem supplement="default">keycloak-datasources.xml</subsystem>
<subsystem>ee.xml</subsystem>
<subsystem>ejb3.xml</subsystem>
<subsystem>io.xml</subsystem>
<subsystem>keycloak-infinispan.xml</subsystem>
<subsystem>jaxrs.xml</subsystem>
<subsystem>jca.xml</subsystem>
<subsystem>jdr.xml</subsystem>
<subsystem supplement="domain">jmx.xml</subsystem>
<subsystem>jpa.xml</subsystem>
<subsystem>jsf.xml</subsystem>
<subsystem>mail.xml</subsystem>
<subsystem>naming.xml</subsystem>
<subsystem>remoting.xml</subsystem>
<subsystem>request-controller.xml</subsystem>
<subsystem>security.xml</subsystem>
<subsystem>security-manager.xml</subsystem>
<subsystem>transactions.xml</subsystem>
<subsystem>undertow.xml</subsystem>
<subsystem>keycloak-server.xml</subsystem>
</subsystems>
<subsystems name="auth-server-clustered">
<!-- Each subsystem to be included relative to the src/main/resources directory -->
<subsystem>logging.xml</subsystem>
<subsystem>bean-validation.xml</subsystem>
<subsystem supplement="domain">keycloak-datasources.xml</subsystem>
<subsystem>ee.xml</subsystem>
<subsystem supplement="ha">ejb3.xml</subsystem>
<subsystem>io.xml</subsystem>
<subsystem supplement="ha">keycloak-infinispan.xml</subsystem>
<subsystem>jaxrs.xml</subsystem>
<subsystem>jca.xml</subsystem>
<subsystem>jdr.xml</subsystem>
<subsystem>jgroups.xml</subsystem>
<subsystem supplement="domain">jmx.xml</subsystem>
<subsystem>jpa.xml</subsystem>
<subsystem>jsf.xml</subsystem>
<subsystem>mail.xml</subsystem>
<subsystem>mod_cluster.xml</subsystem>
<subsystem>naming.xml</subsystem>
<subsystem>remoting.xml</subsystem>
<subsystem>request-controller.xml</subsystem>
<subsystem>security.xml</subsystem>
<subsystem>security-manager.xml</subsystem>
<subsystem>transactions.xml</subsystem>
<subsystem supplement="ha">undertow.xml</subsystem>
<subsystem>keycloak-server.xml</subsystem>
</subsystems>
<subsystems name="auth-server-standalone">
<subsystem>logging.xml</subsystem>
<subsystem>bean-validation.xml</subsystem>
<subsystem>core-management.xml</subsystem>
<subsystem supplement="default">keycloak-datasources.xml</subsystem>
<subsystem>ee.xml</subsystem>
<subsystem>ejb3.xml</subsystem>
<subsystem>io.xml</subsystem>
<subsystem>keycloak-infinispan.xml</subsystem>
<subsystem>jaxrs.xml</subsystem>
<subsystem>jca.xml</subsystem>
<subsystem>jdr.xml</subsystem>
<subsystem supplement="domain">jmx.xml</subsystem>
<subsystem>jpa.xml</subsystem>
<subsystem>jsf.xml</subsystem>
<subsystem>mail.xml</subsystem>
<subsystem>naming.xml</subsystem>
<subsystem>remoting.xml</subsystem>
<subsystem>request-controller.xml</subsystem>
<subsystem supplement="domain-wildfly">elytron.xml</subsystem>
<subsystem>security.xml</subsystem>
<subsystem>security-manager.xml</subsystem>
<subsystem>transactions.xml</subsystem>
<subsystem>undertow.xml</subsystem>
<subsystem>keycloak-server.xml</subsystem>
</subsystems>
<subsystems name="auth-server-clustered">
<!-- Each subsystem to be included relative to the src/main/resources directory -->
<subsystem>logging.xml</subsystem>
<subsystem>bean-validation.xml</subsystem>
<subsystem>core-management.xml</subsystem>
<subsystem supplement="domain">keycloak-datasources.xml</subsystem>
<subsystem>ee.xml</subsystem>
<subsystem supplement="ha">ejb3.xml</subsystem>
<subsystem>io.xml</subsystem>
<subsystem supplement="ha">keycloak-infinispan.xml</subsystem>
<subsystem>jaxrs.xml</subsystem>
<subsystem>jca.xml</subsystem>
<subsystem>jdr.xml</subsystem>
<subsystem>jgroups.xml</subsystem>
<subsystem supplement="domain">jmx.xml</subsystem>
<subsystem>jpa.xml</subsystem>
<subsystem>jsf.xml</subsystem>
<subsystem>mail.xml</subsystem>
<subsystem>mod_cluster.xml</subsystem>
<subsystem>naming.xml</subsystem>
<subsystem>remoting.xml</subsystem>
<subsystem>request-controller.xml</subsystem>
<subsystem supplement="domain-wildfly">elytron.xml</subsystem>
<subsystem>security.xml</subsystem>
<subsystem>security-manager.xml</subsystem>
<subsystem>transactions.xml</subsystem>
<subsystem supplement="ha">undertow.xml</subsystem>
<subsystem>keycloak-server.xml</subsystem>
</subsystems>
<subsystems name="load-balancer">
<subsystem>logging.xml</subsystem>
<subsystem>io.xml</subsystem>
<subsystem>undertow-load-balancer.xml</subsystem>
</subsystems>
</config>

View file

@ -17,7 +17,7 @@
~ limitations under the License.
-->
<domain xmlns="urn:jboss:domain:4.0">
<domain xmlns="urn:jboss:domain:5.0">
<extensions>
<?EXTENSIONS?>
@ -60,31 +60,6 @@
-->
<profile name="load-balancer">
<?SUBSYSTEMS socket-binding-group="load-balancer-sockets"?>
<subsystem xmlns="urn:jboss:domain:undertow:3.0">
<buffer-cache name="default"/>
<server name="default-server">
<http-listener name="default" socket-binding="http" redirect-socket="https"/>
<host name="default-host" alias="localhost">
<location name="/" handler="lb-handler"/>
<filter-ref name="server-header"/>
<filter-ref name="x-powered-by-header"/>
</host>
</server>
<servlet-container name="default">
<jsp-config/>
<websockets/>
</servlet-container>
<handlers>
<reverse-proxy name="lb-handler">
<host name="host1" outbound-socket-binding="remote-host1" scheme="ajp" path="/" instance-id="myroute1"/>
<host name="host2" outbound-socket-binding="remote-host2" scheme="ajp" path="/" instance-id="myroute2"/>
</reverse-proxy>
</handlers>
<filters>
<response-header name="server-header" header-name="Server" header-value="WildFly/10"/>
<response-header name="x-powered-by-header" header-name="X-Powered-By" header-value="Undertow/1"/>
</filters>
</subsystem>
</profile>
</profiles>
@ -96,12 +71,8 @@
These default configurations require the binding specification to be done in host.xml.
-->
<interfaces>
<interface name="management">
<inet-address value="${jboss.bind.address.management:127.0.0.1}"/>
</interface>
<interface name="public">
<inet-address value="${jboss.bind.address:127.0.0.1}"/>
</interface>
<interface name="management"/>
<interface name="public"/>
<?INTERFACES?>
</interfaces>
@ -114,20 +85,19 @@
</socket-binding-group>
<!-- load-balancer-sockets should be removed in production systems and replaced with a better softare or hardare based one -->
<socket-binding-group name="load-balancer-sockets" default-interface="public">
<socket-binding name="ajp" port="${jboss.ajp.port:8009}"/>
<socket-binding name="http" port="${jboss.http.port:8080}"/>
<socket-binding name="https" port="${jboss.https.port:8443}"/>
<outbound-socket-binding name="remote-host1">
<remote-destination host="localhost" port="8159"/>
</outbound-socket-binding>
<outbound-socket-binding name="remote-host2">
<remote-destination host="localhost" port="8259"/>
</outbound-socket-binding>
<!-- Needed for server groups using the 'load-balancer' profile -->
<?SOCKET-BINDINGS?>
</socket-binding-group>
</socket-binding-groups>
<server-groups>
<server-group name="auth-server-group" profile="auth-server-clustered">
<jvm name="default">
<heap size="64m" max-size="512m"/>
</jvm>
<socket-binding-group ref="ha-sockets"/>
</server-group>
<!-- load-balancer-group should be removed in production systems and replaced with a better softare or hardare based one -->
<server-group name="load-balancer-group" profile="load-balancer">
<jvm name="default">
@ -135,12 +105,6 @@
</jvm>
<socket-binding-group ref="load-balancer-sockets"/>
</server-group>
<server-group name="auth-server-group" profile="auth-server-clustered">
<jvm name="default">
<heap size="64m" max-size="512m"/>
</jvm>
<socket-binding-group ref="ha-sockets"/>
</server-group>
</server-groups>
</domain>

View file

@ -22,7 +22,7 @@
is also started by this host controller file. The other instance must be started
via host-slave.xml
-->
<host name="master" xmlns="urn:jboss:domain:4.0">
<host name="master" xmlns="urn:jboss:domain:5.0">
<extensions>
<?EXTENSIONS?>
</extensions>
@ -39,6 +39,11 @@
</authorization>
</security-realm>
<security-realm name="ApplicationRealm">
<server-identities>
<ssl>
<keystore path="application.keystore" relative-to="jboss.domain.config.dir" keystore-password="password" alias="server" key-password="password" generate-self-signed-certificate-host="localhost"/>
</ssl>
</server-identities>
<authentication>
<local default-user="$local" allowed-users="*" skip-group-loading="true"/>
<properties path="application-users.properties" relative-to="jboss.domain.config.dir"/>
@ -53,8 +58,8 @@
<json-formatter name="json-formatter"/>
</formatters>
<handlers>
<file-handler name="host-file" formatter="json-formatter" relative-to="jboss.domain.data.dir" path="audit-log.log"/>
<file-handler name="server-file" formatter="json-formatter" relative-to="jboss.server.data.dir" path="audit-log.log"/>
<file-handler name="host-file" formatter="json-formatter" path="audit-log.log" relative-to="jboss.domain.data.dir"/>
<file-handler name="server-file" formatter="json-formatter" path="audit-log.log" relative-to="jboss.server.data.dir"/>
</handlers>
<logger log-boot="true" log-read-only="false" enabled="false">
<handlers>
@ -71,7 +76,8 @@
<native-interface security-realm="ManagementRealm">
<socket interface="management" port="${jboss.management.native.port:9999}"/>
</native-interface>
<http-interface security-realm="ManagementRealm" http-upgrade-enabled="true">
<http-interface security-realm="ManagementRealm">
<http-upgrade enabled="true" />
<socket interface="management" port="${jboss.management.http.port:9990}"/>
</http-interface>
</management-interfaces>
@ -98,6 +104,8 @@
<heap size="64m" max-size="256m"/>
<jvm-options>
<option value="-server"/>
<option value="-XX:MetaspaceSize=96m"/>
<option value="-XX:MaxMetaspaceSize=256m"/>
</jvm-options>
</jvm>
</jvms>

View file

@ -17,7 +17,7 @@
~ limitations under the License.
-->
<host xmlns="urn:jboss:domain:4.0">
<host xmlns="urn:jboss:domain:5.0">
<extensions>
<?EXTENSIONS?>
</extensions>
@ -27,7 +27,7 @@
<security-realm name="ManagementRealm">
<server-identities>
<!-- Replace this with either a base64 password of your own, or use a vault with a vault expression -->
<secret value="c2xhdmVfdXNlcl9wYXNzd29yZA=="/>
<secret value="c2xhdmVfdXMzcl9wYXNzd29yZA=="/>
</server-identities>
<authentication>
@ -39,6 +39,11 @@
</authorization>
</security-realm>
<security-realm name="ApplicationRealm">
<server-identities>
<ssl>
<keystore path="application.keystore" relative-to="jboss.domain.config.dir" keystore-password="password" alias="server" key-password="password" generate-self-signed-certificate-host="localhost"/>
</ssl>
</server-identities>
<authentication>
<local default-user="$local" allowed-users="*" skip-group-loading="true"/>
<properties path="application-users.properties" relative-to="jboss.domain.config.dir"/>
@ -53,8 +58,8 @@
<json-formatter name="json-formatter"/>
</formatters>
<handlers>
<file-handler name="host-file" formatter="json-formatter" relative-to="jboss.domain.data.dir" path="audit-log.log"/>
<file-handler name="server-file" formatter="json-formatter" relative-to="jboss.server.data.dir" path="audit-log.log"/>
<file-handler name="host-file" formatter="json-formatter" path="audit-log.log" relative-to="jboss.domain.data.dir"/>
<file-handler name="server-file" formatter="json-formatter" path="audit-log.log" relative-to="jboss.server.data.dir"/>
</handlers>
<logger log-boot="true" log-read-only="false" enabled="false">
<handlers>
@ -69,15 +74,15 @@
</audit-log>
<management-interfaces>
<native-interface security-realm="ManagementRealm">
<socket interface="management" port="${jboss.management.native.port:3456}"/>
<socket interface="management" port="${jboss.management.native.port:9999}"/>
</native-interface>
</management-interfaces>
</management>
<domain-controller>
<remote security-realm="ManagementRealm">
<remote username="$local" security-realm="ManagementRealm">
<discovery-options>
<static-discovery name="primary" protocol="${jboss.domain.master.protocol:remote}" host="${jboss.domain.master.address:127.0.0.1}" port="${jboss.domain.master.port:9999}"/>
<static-discovery name="primary" protocol="${jboss.domain.master.protocol:remote}" host="${jboss.domain.master.address}" port="${jboss.domain.master.port:9999}"/>
</discovery-options>
</remote>
</domain-controller>
@ -99,6 +104,8 @@
<heap size="64m" max-size="256m"/>
<jvm-options>
<option value="-server"/>
<option value="-XX:MetaspaceSize=96m"/>
<option value="-XX:MaxMetaspaceSize=256m"/>
</jvm-options>
</jvm>
</jvms>

View file

@ -23,7 +23,7 @@
via host-slave.xml
-->
<host name="master" xmlns="urn:jboss:domain:4.0">
<host name="master" xmlns="urn:jboss:domain:5.0">
<extensions>
<?EXTENSIONS?>
</extensions>
@ -40,6 +40,11 @@
</authorization>
</security-realm>
<security-realm name="ApplicationRealm">
<server-identities>
<ssl>
<keystore path="application.keystore" relative-to="jboss.domain.config.dir" keystore-password="password" alias="server" key-password="password" generate-self-signed-certificate-host="localhost"/>
</ssl>
</server-identities>
<authentication>
<local default-user="$local" allowed-users="*" skip-group-loading="true"/>
<properties path="application-users.properties" relative-to="jboss.domain.config.dir"/>
@ -54,8 +59,8 @@
<json-formatter name="json-formatter"/>
</formatters>
<handlers>
<file-handler name="host-file" formatter="json-formatter" relative-to="jboss.domain.data.dir" path="audit-log.log"/>
<file-handler name="server-file" formatter="json-formatter" relative-to="jboss.server.data.dir" path="audit-log.log"/>
<file-handler name="host-file" formatter="json-formatter" path="audit-log.log" relative-to="jboss.domain.data.dir"/>
<file-handler name="server-file" formatter="json-formatter" path="audit-log.log" relative-to="jboss.server.data.dir"/>
</handlers>
<logger log-boot="true" log-read-only="false" enabled="false">
<handlers>
@ -72,7 +77,8 @@
<native-interface security-realm="ManagementRealm">
<socket interface="management" port="${jboss.management.native.port:9999}"/>
</native-interface>
<http-interface security-realm="ManagementRealm" http-upgrade-enabled="true">
<http-interface security-realm="ManagementRealm">
<http-upgrade enabled="true" />
<socket interface="management" port="${jboss.management.http.port:9990}"/>
</http-interface>
</management-interfaces>
@ -80,6 +86,8 @@
<domain-controller>
<local/>
<!-- Alternative remote domain controller configuration with a host and port -->
<!-- <remote protocol="remote" host="${jboss.domain.master.address}" port="${jboss.domain.master.port:9999}" security-realm="ManagementRealm"/> -->
</domain-controller>
<interfaces>
@ -99,6 +107,8 @@
<heap size="64m" max-size="256m"/>
<jvm-options>
<option value="-server"/>
<option value="-XX:MetaspaceSize=96m"/>
<option value="-XX:MaxMetaspaceSize=256m"/>
</jvm-options>
</jvm>
</jvms>

View file

@ -19,6 +19,8 @@
<!-- See src/resources/configuration/ReadMe.txt for how the configuration assembly works -->
<config>
<subsystems>
<subsystem>core-management.xml</subsystem>
<subsystem>jmx.xml</subsystem>
<subsystem supplement="host">elytron.xml</subsystem>
</subsystems>
</config>

View file

@ -17,19 +17,18 @@
*/
package org.keycloak.example.photoz.admin;
import org.keycloak.example.photoz.entity.Album;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import javax.inject.Inject;
import javax.persistence.EntityManager;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.Response;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import org.keycloak.example.photoz.entity.Album;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
@ -37,14 +36,9 @@ import java.util.List;
@Path("/admin/album")
public class AdminAlbumService {
public static final String SCOPE_ADMIN_ALBUM_MANAGE = "urn:photoz.com:scopes:album:admin:manage";
@Inject
private EntityManager entityManager;
@Context
private HttpHeaders headers;
@GET
@Produces("application/json")
public Response findAll() {

View file

@ -1,15 +1,14 @@
package org.keycloak.example.photoz.album;
import org.keycloak.KeycloakSecurityContext;
import org.keycloak.authorization.client.AuthzClient;
import org.keycloak.authorization.client.Configuration;
import org.keycloak.authorization.client.ClientAuthorizationContext;
import org.keycloak.authorization.client.representation.ResourceRepresentation;
import org.keycloak.authorization.client.representation.ScopeRepresentation;
import org.keycloak.authorization.client.resource.ProtectionResource;
import org.keycloak.example.photoz.ErrorResponse;
import org.keycloak.example.photoz.entity.Album;
import org.keycloak.example.photoz.util.Transaction;
import org.keycloak.representations.adapters.config.AdapterConfig;
import org.keycloak.util.JsonSerialization;
import javax.inject.Inject;
import javax.persistence.EntityManager;
@ -35,7 +34,6 @@ import java.util.Set;
public class AlbumService {
public static final String SCOPE_ALBUM_VIEW = "urn:photoz.com:scopes:album:view";
public static final String SCOPE_ALBUM_CREATE = "urn:photoz.com:scopes:album:create";
public static final String SCOPE_ALBUM_DELETE = "urn:photoz.com:scopes:album:delete";
@Inject
@ -44,12 +42,6 @@ public class AlbumService {
@Context
private HttpServletRequest request;
private AuthzClient authzClient;
public AlbumService() {
}
@POST
@Consumes("application/json")
public Response create(Album newAlbum) {
@ -142,17 +134,14 @@ public class AlbumService {
}
private AuthzClient getAuthzClient() {
if (this.authzClient == null) {
try {
AdapterConfig adapterConfig = JsonSerialization.readValue(this.request.getServletContext().getResourceAsStream("/WEB-INF/keycloak.json"), AdapterConfig.class);
Configuration configuration = new Configuration(adapterConfig.getAuthServerUrl(), adapterConfig.getRealm(), adapterConfig.getResource(), adapterConfig.getCredentials(), null);
return getAuthorizationContext().getClient();
}
this.authzClient = AuthzClient.create(configuration);
} catch (Exception e) {
throw new RuntimeException("Could not create authorization client.", e);
}
}
private ClientAuthorizationContext getAuthorizationContext() {
return ClientAuthorizationContext.class.cast(getKeycloakSecurityContext().getAuthorizationContext());
}
return this.authzClient;
private KeycloakSecurityContext getKeycloakSecurityContext() {
return KeycloakSecurityContext.class.cast(request.getAttribute(KeycloakSecurityContext.class.getName()));
}
}

View file

@ -30,7 +30,7 @@ public class LDAPStorageMapperSpi implements Spi {
@Override
public boolean isInternal() {
return false;
return true;
}
@Override

View file

@ -0,0 +1,50 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.admin.client.resource;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import org.jboss.resteasy.annotations.cache.NoCache;
import org.keycloak.representations.idm.authorization.AggregatePolicyRepresentation;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
public interface AggregatePoliciesResource {
@POST
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
Response create(AggregatePolicyRepresentation representation);
@Path("{id}")
AggregatePolicyResource findById(@PathParam("id") String id);
@Path("/search")
@GET
@Produces(MediaType.APPLICATION_JSON)
@NoCache
AggregatePolicyRepresentation findByName(@QueryParam("name") String name);
}

View file

@ -0,0 +1,69 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.admin.client.resource;
import java.util.List;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import org.jboss.resteasy.annotations.cache.NoCache;
import org.keycloak.representations.idm.authorization.AggregatePolicyRepresentation;
import org.keycloak.representations.idm.authorization.PolicyRepresentation;
import org.keycloak.representations.idm.authorization.ResourceRepresentation;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
public interface AggregatePolicyResource {
@GET
@Produces(MediaType.APPLICATION_JSON)
@NoCache
AggregatePolicyRepresentation toRepresentation();
@PUT
@Consumes(MediaType.APPLICATION_JSON)
void update(AggregatePolicyRepresentation representation);
@DELETE
void remove();
@Path("/associatedPolicies")
@GET
@Produces(MediaType.APPLICATION_JSON)
@NoCache
List<PolicyRepresentation> associatedPolicies();
@Path("/dependentPolicies")
@GET
@Produces(MediaType.APPLICATION_JSON)
@NoCache
List<PolicyRepresentation> dependentPolicies();
@Path("/resources")
@GET
@Produces("application/json")
@NoCache
List<ResourceRepresentation> resources();
}

View file

@ -0,0 +1,50 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.admin.client.resource;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import org.jboss.resteasy.annotations.cache.NoCache;
import org.keycloak.representations.idm.authorization.ClientPolicyRepresentation;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
public interface ClientPoliciesResource {
@POST
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
Response create(ClientPolicyRepresentation representation);
@Path("{id}")
ClientPolicyResource findById(@PathParam("id") String id);
@Path("/search")
@GET
@Produces(MediaType.APPLICATION_JSON)
@NoCache
ClientPolicyRepresentation findByName(@QueryParam("name") String name);
}

View file

@ -0,0 +1,69 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.admin.client.resource;
import java.util.List;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import org.jboss.resteasy.annotations.cache.NoCache;
import org.keycloak.representations.idm.authorization.ClientPolicyRepresentation;
import org.keycloak.representations.idm.authorization.PolicyRepresentation;
import org.keycloak.representations.idm.authorization.ResourceRepresentation;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
public interface ClientPolicyResource {
@GET
@Produces(MediaType.APPLICATION_JSON)
@NoCache
ClientPolicyRepresentation toRepresentation();
@PUT
@Consumes(MediaType.APPLICATION_JSON)
void update(ClientPolicyRepresentation representation);
@DELETE
void remove();
@Path("/associatedPolicies")
@GET
@Produces(MediaType.APPLICATION_JSON)
@NoCache
List<PolicyRepresentation> associatedPolicies();
@Path("/dependentPolicies")
@GET
@Produces(MediaType.APPLICATION_JSON)
@NoCache
List<PolicyRepresentation> dependentPolicies();
@Path("/resources")
@GET
@Produces("application/json")
@NoCache
List<ResourceRepresentation> resources();
}

View file

@ -0,0 +1,50 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.admin.client.resource;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import org.jboss.resteasy.annotations.cache.NoCache;
import org.keycloak.representations.idm.authorization.JSPolicyRepresentation;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
public interface JSPoliciesResource {
@POST
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
Response create(JSPolicyRepresentation representation);
@Path("{id}")
JSPolicyResource findById(@PathParam("id") String id);
@Path("/search")
@GET
@Produces(MediaType.APPLICATION_JSON)
@NoCache
JSPolicyRepresentation findByName(@QueryParam("name") String name);
}

View file

@ -0,0 +1,70 @@
/*
* 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.admin.client.resource;
import java.util.List;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import org.jboss.resteasy.annotations.cache.NoCache;
import org.keycloak.representations.idm.authorization.JSPolicyRepresentation;
import org.keycloak.representations.idm.authorization.PolicyRepresentation;
import org.keycloak.representations.idm.authorization.ResourceRepresentation;
import org.keycloak.representations.idm.authorization.RolePolicyRepresentation;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
public interface JSPolicyResource {
@GET
@Produces(MediaType.APPLICATION_JSON)
@NoCache
JSPolicyRepresentation toRepresentation();
@PUT
@Consumes(MediaType.APPLICATION_JSON)
void update(JSPolicyRepresentation representation);
@DELETE
void remove();
@Path("/associatedPolicies")
@GET
@Produces(MediaType.APPLICATION_JSON)
@NoCache
List<PolicyRepresentation> associatedPolicies();
@Path("/dependentPolicies")
@GET
@Produces(MediaType.APPLICATION_JSON)
@NoCache
List<PolicyRepresentation> dependentPolicies();
@Path("/resources")
@GET
@Produces("application/json")
@NoCache
List<ResourceRepresentation> resources();
}

View file

@ -70,8 +70,23 @@ public interface PoliciesResource {
PolicyEvaluationResponse evaluate(PolicyEvaluationRequest evaluationRequest);
@Path("role")
RolePoliciesResource roles();
RolePoliciesResource role();
@Path("user")
UserPoliciesResource users();
UserPoliciesResource user();
@Path("js")
JSPoliciesResource js();
@Path("time")
TimePoliciesResource time();
@Path("aggregate")
AggregatePoliciesResource aggregate();
@Path("rules")
RulePoliciesResource rule();
@Path("client")
ClientPoliciesResource client();
}

View file

@ -0,0 +1,50 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.admin.client.resource;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import org.jboss.resteasy.annotations.cache.NoCache;
import org.keycloak.representations.idm.authorization.RulePolicyRepresentation;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
public interface RulePoliciesResource {
@POST
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
Response create(RulePolicyRepresentation representation);
@Path("{id}")
RulePolicyResource findById(@PathParam("id") String id);
@Path("/search")
@GET
@Produces(MediaType.APPLICATION_JSON)
@NoCache
RulePolicyRepresentation findByName(@QueryParam("name") String name);
}

View file

@ -0,0 +1,69 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.admin.client.resource;
import java.util.List;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import org.jboss.resteasy.annotations.cache.NoCache;
import org.keycloak.representations.idm.authorization.RulePolicyRepresentation;
import org.keycloak.representations.idm.authorization.PolicyRepresentation;
import org.keycloak.representations.idm.authorization.ResourceRepresentation;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
public interface RulePolicyResource {
@GET
@Produces(MediaType.APPLICATION_JSON)
@NoCache
RulePolicyRepresentation toRepresentation();
@PUT
@Consumes(MediaType.APPLICATION_JSON)
void update(RulePolicyRepresentation representation);
@DELETE
void remove();
@Path("/associatedPolicies")
@GET
@Produces(MediaType.APPLICATION_JSON)
@NoCache
List<PolicyRepresentation> associatedPolicies();
@Path("/dependentPolicies")
@GET
@Produces(MediaType.APPLICATION_JSON)
@NoCache
List<PolicyRepresentation> dependentPolicies();
@Path("/resources")
@GET
@Produces("application/json")
@NoCache
List<ResourceRepresentation> resources();
}

View file

@ -0,0 +1,50 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.admin.client.resource;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import org.jboss.resteasy.annotations.cache.NoCache;
import org.keycloak.representations.idm.authorization.TimePolicyRepresentation;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
public interface TimePoliciesResource {
@POST
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
Response create(TimePolicyRepresentation representation);
@Path("{id}")
TimePolicyResource findById(@PathParam("id") String id);
@Path("/search")
@GET
@Produces(MediaType.APPLICATION_JSON)
@NoCache
TimePolicyRepresentation findByName(@QueryParam("name") String name);
}

View file

@ -0,0 +1,69 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.admin.client.resource;
import java.util.List;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import org.jboss.resteasy.annotations.cache.NoCache;
import org.keycloak.representations.idm.authorization.PolicyRepresentation;
import org.keycloak.representations.idm.authorization.ResourceRepresentation;
import org.keycloak.representations.idm.authorization.TimePolicyRepresentation;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
public interface TimePolicyResource {
@GET
@Produces(MediaType.APPLICATION_JSON)
@NoCache
TimePolicyRepresentation toRepresentation();
@PUT
@Consumes(MediaType.APPLICATION_JSON)
void update(TimePolicyRepresentation representation);
@DELETE
void remove();
@Path("/associatedPolicies")
@GET
@Produces(MediaType.APPLICATION_JSON)
@NoCache
List<PolicyRepresentation> associatedPolicies();
@Path("/dependentPolicies")
@GET
@Produces(MediaType.APPLICATION_JSON)
@NoCache
List<PolicyRepresentation> dependentPolicies();
@Path("/resources")
@GET
@Produces("application/json")
@NoCache
List<ResourceRepresentation> resources();
}

View file

@ -30,7 +30,7 @@ public class JpaEntitySpi implements Spi {
@Override
public boolean isInternal() {
return false;
return true;
}
@Override

View file

@ -23,6 +23,7 @@ import org.keycloak.component.ComponentModel;
import org.keycloak.credential.CredentialModel;
import org.keycloak.credential.UserCredentialStore;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientTemplateModel;
import org.keycloak.models.FederatedIdentityModel;
import org.keycloak.models.GroupModel;
import org.keycloak.models.KeycloakSession;
@ -284,9 +285,25 @@ public class JpaUserProvider implements UserProvider, UserCredentialStore {
Collection<UserConsentProtocolMapperEntity> grantedProtocolMapperEntities = entity.getGrantedProtocolMappers();
if (grantedProtocolMapperEntities != null) {
ClientTemplateModel clientTemplate = null;
if (client.useTemplateMappers()) {
clientTemplate = client.getClientTemplate();
}
for (UserConsentProtocolMapperEntity grantedProtMapper : grantedProtocolMapperEntities) {
ProtocolMapperModel protocolMapper = client.getProtocolMapperById(grantedProtMapper.getProtocolMapperId());
model.addGrantedProtocolMapper(protocolMapper );
// Fallback to client template
if (protocolMapper == null) {
if (clientTemplate != null) {
protocolMapper = clientTemplate.getProtocolMapperById(grantedProtMapper.getProtocolMapperId());
}
}
if (protocolMapper != null) {
model.addGrantedProtocolMapper(protocolMapper);
}
}
}
@ -410,6 +427,18 @@ public class JpaUserProvider implements UserProvider, UserCredentialStore {
.setParameter("realmId", realm.getId())
.setParameter("link", storageProviderId)
.executeUpdate();
num = em.createNamedQuery("deleteUserConsentProtMappersByRealmAndLink")
.setParameter("realmId", realm.getId())
.setParameter("link", storageProviderId)
.executeUpdate();
num = em.createNamedQuery("deleteUserConsentRolesByRealmAndLink")
.setParameter("realmId", realm.getId())
.setParameter("link", storageProviderId)
.executeUpdate();
num = em.createNamedQuery("deleteUserConsentsByRealmAndLink")
.setParameter("realmId", realm.getId())
.setParameter("link", storageProviderId)
.executeUpdate();
num = em.createNamedQuery("deleteUsersByRealmAndLink")
.setParameter("realmId", realm.getId())
.setParameter("link", storageProviderId)

View file

@ -45,6 +45,7 @@ import java.util.Collection;
@NamedQuery(name="userConsentByUserAndClient", query="select consent from UserConsentEntity consent where consent.user.id = :userId and consent.clientId = :clientId"),
@NamedQuery(name="userConsentsByUser", query="select consent from UserConsentEntity consent where consent.user.id = :userId"),
@NamedQuery(name="deleteUserConsentsByRealm", query="delete from UserConsentEntity consent where consent.user IN (select user from UserEntity user where user.realmId = :realmId)"),
@NamedQuery(name="deleteUserConsentsByRealmAndLink", query="delete from UserConsentEntity consent where consent.user IN (select u from UserEntity u where u.realmId=:realmId and u.federationLink=:link)"),
@NamedQuery(name="deleteUserConsentsByUser", query="delete from UserConsentEntity consent where consent.user = :user"),
@NamedQuery(name="deleteUserConsentsByClient", query="delete from UserConsentEntity consent where consent.clientId = :clientId"),
})

View file

@ -36,6 +36,7 @@ import java.io.Serializable;
@NamedQuery(name="deleteUserConsentProtMappersByRealm", query=
"delete from UserConsentProtocolMapperEntity csm where csm.userConsent IN (select consent from UserConsentEntity consent where consent.user IN (select user from UserEntity user where user.realmId = :realmId))"),
@NamedQuery(name="deleteUserConsentProtMappersByUser", query="delete from UserConsentProtocolMapperEntity csm where csm.userConsent IN (select consent from UserConsentEntity consent where consent.user = :user)"),
@NamedQuery(name="deleteUserConsentProtMappersByRealmAndLink", query="delete from UserConsentProtocolMapperEntity csm where csm.userConsent IN (select consent from UserConsentEntity consent where consent.user IN (select u from UserEntity u where u.realmId=:realmId and u.federationLink=:link))"),
@NamedQuery(name="deleteUserConsentProtMappersByProtocolMapper", query="delete from UserConsentProtocolMapperEntity csm where csm.protocolMapperId = :protocolMapperId)"),
@NamedQuery(name="deleteUserConsentProtMappersByClient", query="delete from UserConsentProtocolMapperEntity csm where csm.userConsent IN (select consent from UserConsentEntity consent where consent.clientId = :clientId))"),
})

View file

@ -34,6 +34,7 @@ import java.io.Serializable;
*/
@NamedQueries({
@NamedQuery(name="deleteUserConsentRolesByRealm", query="delete from UserConsentRoleEntity grantedRole where grantedRole.userConsent IN (select consent from UserConsentEntity consent where consent.user IN (select user from UserEntity user where user.realmId = :realmId))"),
@NamedQuery(name="deleteUserConsentRolesByRealmAndLink", query="delete from UserConsentRoleEntity grantedRole where grantedRole.userConsent IN (select consent from UserConsentEntity consent where consent.user IN (select u from UserEntity u where u.realmId=:realmId and u.federationLink=:link))"),
@NamedQuery(name="deleteUserConsentRolesByUser", query="delete from UserConsentRoleEntity grantedRole where grantedRole.userConsent IN (select consent from UserConsentEntity consent where consent.user = :user)"),
@NamedQuery(name="deleteUserConsentRolesByRole", query="delete from UserConsentRoleEntity grantedRole where grantedRole.roleId = :roleId)"),
@NamedQuery(name="deleteUserConsentRolesByClient", query="delete from UserConsentRoleEntity grantedRole where grantedRole.userConsent IN (select consent from UserConsentEntity consent where consent.clientId = :clientId)"),

View file

@ -68,6 +68,11 @@ public class SAML2AuthnRequestBuilder implements SamlProtocolExtensionsAwareBuil
return this;
}
public SAML2AuthnRequestBuilder assertionConsumerUrl(URI assertionConsumerUrl) {
this.authnRequestType.setAssertionConsumerServiceURL(assertionConsumerUrl);
return this;
}
public SAML2AuthnRequestBuilder forceAuthn(boolean forceAuthn) {
this.authnRequestType.setForceAuthn(forceAuthn);
return this;

View file

@ -28,7 +28,7 @@ public class ClientAuthenticatorSpi implements Spi {
@Override
public boolean isInternal() {
return false;
return true;
}
@Override

View file

@ -28,7 +28,7 @@ public class FormActionSpi implements Spi {
@Override
public boolean isInternal() {
return false;
return true;
}
@Override

View file

@ -28,7 +28,7 @@ public class FormAuthenticatorSpi implements Spi {
@Override
public boolean isInternal() {
return false;
return true;
}
@Override

View file

@ -28,7 +28,7 @@ public class RequiredActionSpi implements Spi {
@Override
public boolean isInternal() {
return false;
return true;
}
@Override

View file

@ -29,7 +29,7 @@ public class IdentityProviderSpi implements Spi {
@Override
public boolean isInternal() {
return false;
return true;
}
@Override

View file

@ -29,7 +29,7 @@ public class SocialProviderSpi implements Spi {
@Override
public boolean isInternal() {
return false;
return true;
}
@Override

View file

@ -28,7 +28,7 @@ public class CredentialSpi implements Spi {
@Override
public boolean isInternal() {
return false;
return true;
}
@Override

View file

@ -28,7 +28,7 @@ public class PasswordHashSpi implements Spi {
@Override
public boolean isInternal() {
return false;
return true;
}
@Override

View file

@ -28,7 +28,7 @@ public class EventListenerSpi implements Spi {
@Override
public boolean isInternal() {
return false;
return true;
}
@Override

View file

@ -40,6 +40,7 @@ public class MigrateTo3_1_0 implements Migration {
Map<String, String> browserSecurityHeaders = new HashMap<>(realm.getBrowserSecurityHeaders());
browserSecurityHeaders.put("xRobotsTag", "none");
browserSecurityHeaders.put("xXSSProtection", "1; mode=block");
realm.setBrowserSecurityHeaders(Collections.unmodifiableMap(browserSecurityHeaders));
}

View file

@ -35,12 +35,14 @@ public class BrowserSecurityHeaders {
headerMap.put("contentSecurityPolicy", "Content-Security-Policy");
headerMap.put("xContentTypeOptions", "X-Content-Type-Options");
headerMap.put("xRobotsTag", "X-Robots-Tag");
headerMap.put("xXSSProtection", "X-XSS-Protection");
Map<String, String> dh = new HashMap<>();
dh.put("xFrameOptions", "SAMEORIGIN");
dh.put("contentSecurityPolicy", "frame-src 'self'");
dh.put("xContentTypeOptions", "nosniff");
dh.put("xRobotsTag", "none");
dh.put("xXSSProtection", "1; mode=block");
defaultHeaders = Collections.unmodifiableMap(dh);
headerAttributeMap = Collections.unmodifiableMap(headerMap);

View file

@ -19,7 +19,6 @@ package org.keycloak.models.utils;
import org.keycloak.broker.social.SocialIdentityProvider;
import org.keycloak.broker.social.SocialIdentityProviderFactory;
import org.keycloak.common.util.Base64Url;
import org.keycloak.common.util.CertificateUtils;
import org.keycloak.common.util.KeyUtils;
import org.keycloak.common.util.PemUtils;
@ -75,14 +74,14 @@ public final class KeycloakModelUtils {
return UUID.randomUUID().toString();
}
public static String generateSecret() {
public static byte[] generateSecret() {
return generateSecret(32);
}
public static String generateSecret(int bytes) {
public static byte[] generateSecret(int bytes) {
byte[] buf = new byte[bytes];
new SecureRandom().nextBytes(buf);
return Base64Url.encode(buf);
return buf;
}
public static PublicKey getPublicKey(String publicKeyPem) {

View file

@ -28,7 +28,7 @@ public class PasswordPolicySpi implements Spi {
@Override
public boolean isInternal() {
return false;
return true;
}
@Override

View file

@ -18,6 +18,7 @@
package org.keycloak.services.managers;
import org.jboss.logging.Logger;
import org.keycloak.common.util.Base64Url;
import org.keycloak.common.util.Time;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientSessionModel;
@ -227,7 +228,7 @@ public class ClientSessionCode {
private static String generateCode(ClientSessionModel clientSession) {
try {
String actionId = KeycloakModelUtils.generateSecret();
String actionId = Base64Url.encode(KeycloakModelUtils.generateSecret());
StringBuilder sb = new StringBuilder();
sb.append(actionId);

View file

@ -65,9 +65,9 @@ public class AbstractPermissionService {
return request.stream().map(request1 -> {
String resourceSetId = request1.getResourceSetId();
String resourceSetName = request1.getResourceSetName();
boolean resourceNotProvider = resourceSetId == null && resourceSetName == null;
boolean resourceNotProvided = resourceSetId == null && resourceSetName == null;
if (resourceNotProvider) {
if (resourceNotProvided) {
if ((request1.getScopes() == null || request1.getScopes().isEmpty())) {
throw new ErrorResponseException("invalid_resource_set_id", "Resource id or name not provided.", Response.Status.BAD_REQUEST);
}
@ -75,7 +75,7 @@ public class AbstractPermissionService {
Resource resource = null;
if (!resourceNotProvider) {
if (!resourceNotProvided) {
if (resourceSetId != null) {
resource = storeFactory.getResourceStore().findById(resourceSetId, resourceServer.getId());
} else {

View file

@ -17,6 +17,7 @@
package org.keycloak.keys;
import org.keycloak.common.util.Base64Url;
import org.keycloak.common.util.KeyUtils;
import org.keycloak.component.ComponentModel;
import org.keycloak.jose.jws.AlgorithmType;
@ -47,7 +48,7 @@ public class GeneratedHmacKeyProvider implements HmacKeyProvider {
if (model.hasNote(SecretKey.class.getName())) {
secretKey = model.getNote(SecretKey.class.getName());
} else {
secretKey = KeyUtils.loadSecretKey(model.get(Attributes.SECRET_KEY));
secretKey = KeyUtils.loadSecretKey(Base64Url.decode(model.get(Attributes.SECRET_KEY)));
model.setNote(SecretKey.class.getName(), secretKey);
}
}

View file

@ -81,8 +81,8 @@ public class GeneratedHmacKeyProviderFactory extends AbstractHmacKeyProviderFact
private void generateSecret(ComponentModel model, int size) {
try {
String secret = KeycloakModelUtils.generateSecret(size);
model.put(Attributes.SECRET_KEY, secret);
byte[] secret = KeycloakModelUtils.generateSecret(size);
model.put(Attributes.SECRET_KEY, Base64Url.encode(secret));
String kid = KeycloakModelUtils.generateId();
model.put(Attributes.KID_KEY, kid);

View file

@ -267,9 +267,7 @@ public class SamlProtocol implements LoginProtocol {
if (logoutPostUrl == null || logoutPostUrl.trim().isEmpty()) {
// if we don't have a redirect uri either, return true and default to the admin url + POST binding
if (logoutRedirectUrl == null || logoutRedirectUrl.trim().isEmpty())
return true;
return false;
return (logoutRedirectUrl == null || logoutRedirectUrl.trim().isEmpty());
}
if (samlClient.forcePostBinding()) {
@ -282,11 +280,8 @@ public class SamlProtocol implements LoginProtocol {
if (SAML_POST_BINDING.equals(bindingType))
return true;
if (logoutRedirectUrl == null || logoutRedirectUrl.trim().isEmpty())
return true; // we don't have a redirect binding url, so use post binding
return false; // redirect binding
// true if we don't have a redirect binding url, so use post binding, false for redirect binding
return (logoutRedirectUrl == null || logoutRedirectUrl.trim().isEmpty());
}
protected String getNameIdFormat(SamlClient samlClient, ClientSessionModel clientSession) {
@ -529,15 +524,20 @@ public class SamlProtocol implements LoginProtocol {
if (!(client instanceof ClientModel))
return null;
try {
if (isLogoutPostBindingForClient(clientSession)) {
String bindingUri = getLogoutServiceUrl(uriInfo, client, SAML_POST_BINDING);
boolean postBinding = isLogoutPostBindingForClient(clientSession);
String bindingUri = getLogoutServiceUrl(uriInfo, client, postBinding ? SAML_POST_BINDING : SAML_REDIRECT_BINDING);
if (bindingUri == null) {
logger.warnf("Failed to logout client %s, skipping this client. Please configure the logout service url in the admin console for your client applications.", client.getClientId());
return null;
}
if (postBinding) {
SAML2LogoutRequestBuilder logoutBuilder = createLogoutRequest(bindingUri, clientSession, client);
// This is POST binding, hence KeyID is included in dsig:KeyInfo/dsig:KeyName, no need to add <samlp:Extensions> element
JaxrsSAML2BindingBuilder binding = createBindingBuilder(samlClient);
return binding.postBinding(logoutBuilder.buildDocument()).request(bindingUri);
} else {
logger.debug("frontchannel redirect binding");
String bindingUri = getLogoutServiceUrl(uriInfo, client, SAML_REDIRECT_BINDING);
SAML2LogoutRequestBuilder logoutBuilder = createLogoutRequest(bindingUri, clientSession, client);
if (samlClient.requiresRealmSignature() && samlClient.addExtensionsElementWithKeyInfo()) {
KeyManager.ActiveRsaKey keys = session.keys().getActiveRsaKey(realm);
@ -620,7 +620,7 @@ public class SamlProtocol implements LoginProtocol {
SamlClient samlClient = new SamlClient(client);
String logoutUrl = getLogoutServiceUrl(uriInfo, client, SAML_POST_BINDING);
if (logoutUrl == null) {
logger.warnv("Can't do backchannel logout. No SingleLogoutService POST Binding registered for client: {1}", client.getClientId());
logger.warnf("Can't do backchannel logout. No SingleLogoutService POST Binding registered for client: %s", client.getClientId());
return;
}
SAML2LogoutRequestBuilder logoutBuilder = createLogoutRequest(logoutUrl, clientSession, client);

View file

@ -359,7 +359,8 @@ public class DefaultKeycloakSessionFactory implements KeycloakSessionFactory, Pr
}
private boolean isInternal(ProviderFactory<?> factory) {
return factory.getClass().getPackage().getName().startsWith("org.keycloak");
String packageName = factory.getClass().getPackage().getName();
return packageName.startsWith("org.keycloak") && !packageName.startsWith("org.keycloak.examples");
}
/**

View file

@ -35,32 +35,32 @@ import java.net.URI;
*/
public class Urls {
public static URI adminConsoleRoot(URI baseUri, String realmId) {
return UriBuilder.fromUri(baseUri).path(AdminRoot.class).path("{realm}/console/").build(realmId);
public static URI adminConsoleRoot(URI baseUri, String realmName) {
return UriBuilder.fromUri(baseUri).path(AdminRoot.class).path("{realm}/console/").build(realmName);
}
public static URI accountApplicationsPage(URI baseUri, String realmId) {
return accountBase(baseUri).path(AccountService.class, "applicationsPage").build(realmId);
public static URI accountApplicationsPage(URI baseUri, String realmName) {
return accountBase(baseUri).path(AccountService.class, "applicationsPage").build(realmName);
}
public static UriBuilder accountBase(URI baseUri) {
return realmBase(baseUri).path(RealmsResource.class, "getAccountService");
}
public static URI accountPage(URI baseUri, String realmId) {
return accountPageBuilder(baseUri).build(realmId);
public static URI accountPage(URI baseUri, String realmName) {
return accountPageBuilder(baseUri).build(realmName);
}
public static UriBuilder accountPageBuilder(URI baseUri) {
return accountBase(baseUri).path(AccountService.class, "accountPage");
}
public static URI accountPasswordPage(URI baseUri, String realmId) {
return accountBase(baseUri).path(AccountService.class, "passwordPage").build(realmId);
public static URI accountPasswordPage(URI baseUri, String realmName) {
return accountBase(baseUri).path(AccountService.class, "passwordPage").build(realmName);
}
public static URI accountFederatedIdentityPage(URI baseUri, String realmId) {
return accountBase(baseUri).path(AccountService.class, "federatedIdentityPage").build(realmId);
public static URI accountFederatedIdentityPage(URI baseUri, String realmName) {
return accountBase(baseUri).path(AccountService.class, "federatedIdentityPage").build(realmName);
}
public static URI accountFederatedIdentityUpdate(URI baseUri, String realmName) {
@ -116,45 +116,45 @@ public class Urls {
.build(realmName);
}
public static URI accountTotpPage(URI baseUri, String realmId) {
return accountBase(baseUri).path(AccountService.class, "totpPage").build(realmId);
public static URI accountTotpPage(URI baseUri, String realmName) {
return accountBase(baseUri).path(AccountService.class, "totpPage").build(realmName);
}
public static URI accountTotpRemove(URI baseUri, String realmId, String stateChecker) {
public static URI accountTotpRemove(URI baseUri, String realmName, String stateChecker) {
return accountBase(baseUri).path(AccountService.class, "processTotpRemove")
.queryParam("stateChecker", stateChecker)
.build(realmId);
.build(realmName);
}
public static URI accountLogPage(URI baseUri, String realmId) {
return accountBase(baseUri).path(AccountService.class, "logPage").build(realmId);
public static URI accountLogPage(URI baseUri, String realmName) {
return accountBase(baseUri).path(AccountService.class, "logPage").build(realmName);
}
public static URI accountSessionsPage(URI baseUri, String realmId) {
return accountBase(baseUri).path(AccountService.class, "sessionsPage").build(realmId);
public static URI accountSessionsPage(URI baseUri, String realmName) {
return accountBase(baseUri).path(AccountService.class, "sessionsPage").build(realmName);
}
public static URI accountSessionsLogoutPage(URI baseUri, String realmId, String stateChecker) {
public static URI accountSessionsLogoutPage(URI baseUri, String realmName, String stateChecker) {
return accountBase(baseUri).path(AccountService.class, "processSessionsLogout")
.queryParam("stateChecker", stateChecker)
.build(realmId);
.build(realmName);
}
public static URI accountRevokeClientPage(URI baseUri, String realmId) {
public static URI accountRevokeClientPage(URI baseUri, String realmName) {
return accountBase(baseUri).path(AccountService.class, "processRevokeGrant")
.build(realmId);
.build(realmName);
}
public static URI accountLogout(URI baseUri, URI redirectUri, String realmId) {
return realmLogout(baseUri).queryParam("redirect_uri", redirectUri).build(realmId);
public static URI accountLogout(URI baseUri, URI redirectUri, String realmName) {
return realmLogout(baseUri).queryParam("redirect_uri", redirectUri).build(realmName);
}
public static URI loginActionUpdatePassword(URI baseUri, String realmId) {
return loginActionsBase(baseUri).path(LoginActionsService.class, "updatePassword").build(realmId);
public static URI loginActionUpdatePassword(URI baseUri, String realmName) {
return loginActionsBase(baseUri).path(LoginActionsService.class, "updatePassword").build(realmName);
}
public static URI loginActionUpdateTotp(URI baseUri, String realmId) {
return loginActionsBase(baseUri).path(LoginActionsService.class, "updateTotp").build(realmId);
public static URI loginActionUpdateTotp(URI baseUri, String realmName) {
return loginActionsBase(baseUri).path(LoginActionsService.class, "updateTotp").build(realmName);
}
public static UriBuilder requiredActionBase(URI baseUri) {
@ -162,20 +162,20 @@ public class Urls {
}
public static URI loginActionUpdateProfile(URI baseUri, String realmId) {
return loginActionsBase(baseUri).path(LoginActionsService.class, "updateProfile").build(realmId);
public static URI loginActionUpdateProfile(URI baseUri, String realmName) {
return loginActionsBase(baseUri).path(LoginActionsService.class, "updateProfile").build(realmName);
}
public static URI loginActionEmailVerification(URI baseUri, String realmId) {
return loginActionEmailVerificationBuilder(baseUri).build(realmId);
public static URI loginActionEmailVerification(URI baseUri, String realmName) {
return loginActionEmailVerificationBuilder(baseUri).build(realmName);
}
public static UriBuilder loginActionEmailVerificationBuilder(URI baseUri) {
return loginActionsBase(baseUri).path(LoginActionsService.class, "emailVerification");
}
public static URI loginResetCredentials(URI baseUri, String realmId) {
return loginResetCredentialsBuilder(baseUri).build(realmId);
public static URI loginResetCredentials(URI baseUri, String realmName) {
return loginResetCredentialsBuilder(baseUri).build(realmName);
}
public static UriBuilder executeActionsBuilder(URI baseUri) {
@ -186,44 +186,44 @@ public class Urls {
return loginActionsBase(baseUri).path(LoginActionsService.RESET_CREDENTIALS_PATH);
}
public static URI loginUsernameReminder(URI baseUri, String realmId) {
return loginUsernameReminderBuilder(baseUri).build(realmId);
public static URI loginUsernameReminder(URI baseUri, String realmName) {
return loginUsernameReminderBuilder(baseUri).build(realmName);
}
public static UriBuilder loginUsernameReminderBuilder(URI baseUri) {
return loginActionsBase(baseUri).path(LoginActionsService.class, "usernameReminder");
}
public static String realmIssuer(URI baseUri, String realmId) {
return realmBase(baseUri).path("{realm}").build(realmId).toString();
public static String realmIssuer(URI baseUri, String realmName) {
return realmBase(baseUri).path("{realm}").build(realmName).toString();
}
public static UriBuilder realmBase(URI baseUri) {
return UriBuilder.fromUri(baseUri).path(RealmsResource.class);
}
public static URI realmLoginPage(URI baseUri, String realmId) {
return loginActionsBase(baseUri).path(LoginActionsService.class, "authenticate").build(realmId);
public static URI realmLoginPage(URI baseUri, String realmName) {
return loginActionsBase(baseUri).path(LoginActionsService.class, "authenticate").build(realmName);
}
private static UriBuilder realmLogout(URI baseUri) {
return tokenBase(baseUri).path(OIDCLoginProtocolService.class, "logout");
}
public static URI realmRegisterAction(URI baseUri, String realmId) {
return loginActionsBase(baseUri).path(LoginActionsService.class, "processRegister").build(realmId);
public static URI realmRegisterAction(URI baseUri, String realmName) {
return loginActionsBase(baseUri).path(LoginActionsService.class, "processRegister").build(realmName);
}
public static URI realmRegisterPage(URI baseUri, String realmId) {
return loginActionsBase(baseUri).path(LoginActionsService.class, "registerPage").build(realmId);
public static URI realmRegisterPage(URI baseUri, String realmName) {
return loginActionsBase(baseUri).path(LoginActionsService.class, "registerPage").build(realmName);
}
public static URI realmInstalledAppUrnCallback(URI baseUri, String realmId) {
return tokenBase(baseUri).path(OIDCLoginProtocolService.class, "installedAppUrnCallback").build(realmId);
public static URI realmInstalledAppUrnCallback(URI baseUri, String realmName) {
return tokenBase(baseUri).path(OIDCLoginProtocolService.class, "installedAppUrnCallback").build(realmName);
}
public static URI realmOauthAction(URI baseUri, String realmId) {
return loginActionsBase(baseUri).path(LoginActionsService.class, "processConsent").build(realmId);
public static URI realmOauthAction(URI baseUri, String realmName) {
return loginActionsBase(baseUri).path(LoginActionsService.class, "processConsent").build(realmName);
}
public static URI firstBrokerLoginProcessor(URI baseUri, String realmName) {

View file

@ -27,7 +27,7 @@ import org.keycloak.provider.Spi;
public class ClientRegistrationSpi implements Spi {
@Override
public boolean isInternal() {
return false;
return true;
}
@Override

View file

@ -22,6 +22,7 @@ import org.jboss.resteasy.spi.HttpRequest;
import org.keycloak.AbstractOAuthClient;
import org.keycloak.OAuth2Constants;
import org.keycloak.common.ClientConnection;
import org.keycloak.common.util.Base64Url;
import org.keycloak.common.util.KeycloakUriBuilder;
import org.keycloak.common.util.UriUtils;
import org.keycloak.models.ClientModel;
@ -133,7 +134,7 @@ public abstract class AbstractSecuredLocalService {
if (cookie != null) {
stateChecker = cookie.getValue();
} else {
stateChecker = KeycloakModelUtils.generateSecret();
stateChecker = Base64Url.encode(KeycloakModelUtils.generateSecret());
String cookiePath = AuthenticationManager.getRealmCookiePath(realm, uriInfo);
boolean secureOnly = realm.getSslRequired().isRequired(clientConnection);
CookieHelper.addCookie(KEYCLOAK_STATE_CHECKER, stateChecker, cookiePath, null, null, -1, secureOnly, true);

View file

@ -19,6 +19,7 @@ package org.keycloak.services.resources;
import org.jboss.logging.Logger;
import org.keycloak.Config;
import org.keycloak.common.ClientConnection;
import org.keycloak.common.util.Base64Url;
import org.keycloak.common.util.MimeTypeUtil;
import org.keycloak.models.BrowserSecurityHeaders;
import org.keycloak.models.KeycloakSession;
@ -246,7 +247,7 @@ public class WelcomeResource {
if (stateChecker != null) {
return stateChecker;
} else {
stateChecker = KeycloakModelUtils.generateSecret();
stateChecker = Base64Url.encode(KeycloakModelUtils.generateSecret());
String cookiePath = uriInfo.getPath();
boolean secureOnly = uriInfo.getRequestUri().getScheme().equalsIgnoreCase("https");
CookieHelper.addCookie(KEYCLOAK_STATE_CHECKER, stateChecker, cookiePath, null, null, -1, secureOnly, true);

View file

@ -296,7 +296,7 @@ public class AdminConsole {
authUrl = authUrl.substring(0, authUrl.length() - 1);
map.put("authUrl", authUrl);
map.put("consoleBaseUrl", Urls.adminConsoleRoot(baseUri, realm.getId()));
map.put("consoleBaseUrl", Urls.adminConsoleRoot(baseUri, realm.getName()));
map.put("resourceUrl", Urls.themeRoot(baseUri) + "/admin/" + theme.getName());
map.put("masterRealm", Config.getAdminRealm());
map.put("resourceVersion", Version.RESOURCES_VERSION);

View file

@ -165,10 +165,10 @@ Assumed you downloaded `jboss-fuse-karaf-6.3.0.redhat-229.zip`
### DB migration test
This test will:
- start Keycloak 1.9.8
- start Keycloak 1.9.8 (replace with the other version if needed)
- import realm and some data to MySQL DB
- stop Keycloak 1.9.8
- start latest KEycloak, which automatically updates DB from 1.9.8
- start latest Keycloak, which automatically updates DB from 1.9.8
- Do some test that data are correct
@ -191,7 +191,41 @@ This test will:
-Dkeycloak.connectionsJpa.url=jdbc:mysql://$DB_HOST/keycloak \
-Dkeycloak.connectionsJpa.user=keycloak \
-Dkeycloak.connectionsJpa.password=keycloak
### DB migration test with manual mode
Same test as above, but it uses manual migration mode. During startup of the new Keycloak server, Liquibase won't automatically perform DB update, but it
just exports the needed SQL into the script. This SQL script then needs to be manually executed against the DB.
1) Prepare MySQL DB (Same as above)
2) Run the test (Update according to your DB connection, versions etc). This step will end with failure, but that's expected:
mvn -f testsuite/integration-arquillian/pom.xml \
clean install \
-Pauth-server-wildfly,jpa,clean-jpa,auth-server-migration \
-Dtest=MigrationTest \
-Dmigration.mode=manual \
-Dmigrated.auth.server.version=1.9.8.Final \
-Djdbc.mvn.groupId=mysql \
-Djdbc.mvn.version=5.1.29 \
-Djdbc.mvn.artifactId=mysql-connector-java \
-Dkeycloak.connectionsJpa.url=jdbc:mysql://$DB_HOST/keycloak \
-Dkeycloak.connectionsJpa.user=keycloak \
-Dkeycloak.connectionsJpa.password=keycloak
3) Manually execute the SQL script against your DB. With Mysql, you can use this command (KEYCLOAK_SRC points to the directory with the Keycloak codebase):
mysql -h $DB_HOST -u keycloak -pkeycloak < $KEYCLOAK_SRC/testsuite/integration-arquillian/tests/base/target/containers/auth-server-wildfly/keycloak-database-update.sql
4) Finally run the migration test, which will verify that DB migration was successful. This should end with success:
mvn -f testsuite/integration-arquillian/tests/base/pom.xml \
clean install \
-Pauth-server-wildfly \
-Dskip.add.user.json=true \
-Dmigrated.auth.server.version=1.9.8.Final \
-Dtest=MigrationTest
### JSON export/import migration test
This will start latest Keycloak and import the realm JSON file, which was previously exported from Keycloak 1.9.8.Final

View file

@ -112,14 +112,17 @@
},
{
"clientId": "photoz-restful-api",
"secret": "secret",
"enabled": true,
"baseUrl": "/photoz-restful-api",
"authorizationServicesEnabled" : true,
"redirectUris": [
"/photoz-restful-api/*"
],
"webOrigins" : ["*"]
"webOrigins" : ["*"],
"clientAuthenticatorType": "client-jwt",
"attributes" : {
"jwt.credential.certificate" : "MIICqTCCAZECBgFT0Ngs/DANBgkqhkiG9w0BAQsFADAYMRYwFAYDVQQDDA1zZWN1cmUtcG9ydGFsMB4XDTE2MDQwMTA4MDA0MVoXDTI2MDQwMTA4MDIyMVowGDEWMBQGA1UEAwwNc2VjdXJlLXBvcnRhbDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJa4GixpmzP511AmI0eLPLORyJwXS8908MUvdG3hmh8jMOIhe28XjIFeZSY09vFxh22F2SUMjxU/B2Hw4PDJUkebuNR7rXhOIYCJAo6eEZzjSBY/wngFtfm74zJ/eLCobBtDvIld7jobdHTfE1Oz9+GzvtG0k7cm7ubrLT0J4I1UsFZj3b//3wa+O0vNaTwHC1Jz/m59VbtXqyO4xEzIdl416cnGCmEmk5qd5h1de2UoLi/CTad8HftIJhzN1qhlySzW/9Ha70aYlDH2hiibDsXDTrNaMdaaLik7I8Rv/nIbggysG863PKZo8wknDe62QctH5VYSSktiy4gjSJkGh7ECAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAZnnx+AHQ8txugGcFK8gWjildDgk+v31fBHBDvmLQaSzsUaIOJaK4wnlwUI+VfR46HmBXhjlDCobFLUptd+kz0G7xapcIn3b5jLrySUUD7L+LAp1vNOQU4mKhTGS3IEvNB73D3GH9rQ+M3KEcoN3f99fNKqKsUdxbmZqGf4VOQ57PUfLBw4PJJGlROPosBc7ivPRyeYnKekhoCTynq30BAD1FA1BA8ppcY4ZVGADPTAgMJxpglpFY9LiqCwdLAGW1ttnsyIJ7DpT+kybhhk7c+MU7gyQdv8xPnMR0bSCB9hndowgBn5oZ393aMscwMNCzwJ0aWBs1sUyn3X0RIsu9Jg=="
}
}
]
}

View file

@ -1,6 +1,8 @@
package org.keycloak.example.photoz.album;
import org.keycloak.KeycloakSecurityContext;
import org.keycloak.authorization.client.AuthzClient;
import org.keycloak.authorization.client.ClientAuthorizationContext;
import org.keycloak.authorization.client.Configuration;
import org.keycloak.authorization.client.representation.ResourceRepresentation;
import org.keycloak.authorization.client.representation.ScopeRepresentation;
@ -38,7 +40,6 @@ public class AlbumService {
private static volatile long nextId = 0;
public static final String SCOPE_ALBUM_VIEW = "urn:photoz.com:scopes:album:view";
public static final String SCOPE_ALBUM_CREATE = "urn:photoz.com:scopes:album:create";
public static final String SCOPE_ALBUM_DELETE = "urn:photoz.com:scopes:album:delete";
@Inject
@ -47,12 +48,6 @@ public class AlbumService {
@Context
private HttpServletRequest request;
private AuthzClient authzClient;
public AlbumService() {
}
@POST
@Consumes("application/json")
public Response create(Album newAlbum, @QueryParam("user") String username) {
@ -148,17 +143,14 @@ public class AlbumService {
}
private AuthzClient getAuthzClient() {
if (this.authzClient == null) {
try {
AdapterConfig adapterConfig = JsonSerialization.readValue(this.request.getServletContext().getResourceAsStream("/WEB-INF/keycloak.json"), AdapterConfig.class);
Configuration configuration = new Configuration(adapterConfig.getAuthServerUrl(), adapterConfig.getRealm(), adapterConfig.getResource(), adapterConfig.getCredentials(), null);
this.authzClient = AuthzClient.create(configuration);
} catch (Exception e) {
throw new RuntimeException("Could not create authorization client.", e);
}
}
return this.authzClient;
return getAuthorizationContext().getClient();
}
}
private ClientAuthorizationContext getAuthorizationContext() {
return ClientAuthorizationContext.class.cast(getKeycloakSecurityContext().getAuthorizationContext());
}
private KeycloakSecurityContext getKeycloakSecurityContext() {
return KeycloakSecurityContext.class.cast(request.getAttribute(KeycloakSecurityContext.class.getName()));
}
}

View file

@ -6,7 +6,14 @@
"resource": "photoz-restful-api",
"bearer-only" : true,
"credentials": {
"secret": "secret"
"jwt": {
"client-key-password": "password",
"client-keystore-file": "classpath:keystore.jks",
"client-keystore-password": "password",
"client-key-alias": "secure-portal",
"token-timeout": 10,
"client-keystore-type": "jks"
}
},
"policy-enforcer": {
"user-managed-access" : {},

View file

@ -115,6 +115,11 @@
{
"name": "Pattern 11",
"typedScopes": []
},
{
"name": "Pattern 12",
"uri": "/realm_uri",
"typedScopes": []
}
],
"policies": [
@ -256,6 +261,16 @@
"resources": "[\"Pattern 11\"]",
"applyPolicies": "[\"Default Policy\"]"
}
},
{
"name": "Pattern 12 Permission",
"type": "resource",
"logic": "POSITIVE",
"decisionStrategy": "UNANIMOUS",
"config": {
"resources": "[\"Pattern 12\"]",
"applyPolicies": "[\"Default Policy\"]"
}
}
],
"scopes": []

View file

@ -56,6 +56,10 @@
{
"name": "Pattern 11",
"path": "/api/{version}/{resource}"
},
{
"name": "Pattern 12",
"path": "/keycloak_json_uri"
}
]
}

View file

@ -0,0 +1,39 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.testsuite.adapter.page;
import org.jboss.arquillian.container.test.api.OperateOnDeployment;
import org.jboss.arquillian.test.api.ArquillianResource;
import java.net.URL;
/**
* @author mhajas
*/
public class EmployeeAcsServlet extends SAMLServlet {
public static final String DEPLOYMENT_NAME = "employee-acs";
@ArquillianResource
@OperateOnDeployment(DEPLOYMENT_NAME)
private URL url;
@Override
public URL getInjectedUrl() {
return url;
}
}

View file

@ -178,7 +178,7 @@ public class AuthServerTestEnricher {
}
}
public void runPreMigrationTask(@Observes(precedence = 2) StartSuiteContainers event) {
public void runPreMigrationTask(@Observes(precedence = 2) StartSuiteContainers event) throws Exception {
if (suiteContext.isAuthServerMigrationEnabled()) {
log.info("\n\n### Run preMigration task on keycloak " + System.getProperty("migrated.auth.server.version", "- previous") + " ###\n\n");
suiteContext.getMigrationContext().runPreMigrationTask();

View file

@ -111,12 +111,15 @@ public class DeploymentArchiveProcessor implements ApplicationArchiveProcessor {
if (authServerSslRequired) {
modifyDocElementAttribute(doc, "SingleSignOnService", "bindingUrl", "8080", System.getProperty("auth.server.https.port"));
modifyDocElementAttribute(doc, "SingleSignOnService", "bindingUrl", "http", "https");
modifyDocElementAttribute(doc, "SingleSignOnService", "assertionConsumerServiceUrl", "8081", System.getProperty("app.server.http.port"));
modifyDocElementAttribute(doc, "SingleSignOnService", "assertionConsumerServiceUrl", "http", "https");
modifyDocElementAttribute(doc, "SingleLogoutService", "postBindingUrl", "8080", System.getProperty("auth.server.https.port"));
modifyDocElementAttribute(doc, "SingleLogoutService", "postBindingUrl", "http", "https");
modifyDocElementAttribute(doc, "SingleLogoutService", "redirectBindingUrl", "8080", System.getProperty("auth.server.https.port"));
modifyDocElementAttribute(doc, "SingleLogoutService", "redirectBindingUrl", "http", "https");
} else {
modifyDocElementAttribute(doc, "SingleSignOnService", "bindingUrl", "8080", System.getProperty("auth.server.http.port"));
modifyDocElementAttribute(doc, "SingleSignOnService", "assertionConsumerServiceUrl", "8081", System.getProperty("app.server.http.port"));
modifyDocElementAttribute(doc, "SingleLogoutService", "postBindingUrl", "8080", System.getProperty("auth.server.http.port"));
modifyDocElementAttribute(doc, "SingleLogoutService", "redirectBindingUrl", "8080", System.getProperty("auth.server.http.port"));
}

Some files were not shown because too many files have changed in this diff Show more