merge conflicts

This commit is contained in:
Bill Burke 2015-09-23 21:01:47 -04:00
commit c14d3d7963
608 changed files with 23984 additions and 3992 deletions

View file

@ -240,6 +240,7 @@ public class SAMLEndpoint {
.relayState(relayState); .relayState(relayState);
if (config.isWantAuthnRequestsSigned()) { if (config.isWantAuthnRequestsSigned()) {
binding.signWith(realm.getPrivateKey(), realm.getPublicKey(), realm.getCertificate()) binding.signWith(realm.getPrivateKey(), realm.getPublicKey(), realm.getCertificate())
.signatureAlgorithm(provider.getSignatureAlgorithm())
.signDocument(); .signDocument();
} }
try { try {

View file

@ -37,6 +37,7 @@ import org.keycloak.protocol.saml.JaxrsSAML2BindingBuilder;
import org.keycloak.saml.SAML2AuthnRequestBuilder; import org.keycloak.saml.SAML2AuthnRequestBuilder;
import org.keycloak.saml.SAML2LogoutRequestBuilder; import org.keycloak.saml.SAML2LogoutRequestBuilder;
import org.keycloak.saml.SAML2NameIDPolicyBuilder; import org.keycloak.saml.SAML2NameIDPolicyBuilder;
import org.keycloak.saml.SignatureAlgorithm;
import org.keycloak.saml.common.constants.GeneralConstants; import org.keycloak.saml.common.constants.GeneralConstants;
import org.keycloak.saml.common.constants.JBossSAMLURIConstants; import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
@ -108,6 +109,7 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
KeyPair keypair = new KeyPair(publicKey, privateKey); KeyPair keypair = new KeyPair(publicKey, privateKey);
binding.signWith(keypair); binding.signWith(keypair);
binding.signatureAlgorithm(getSignatureAlgorithm());
binding.signDocument(); binding.signDocument();
} }
@ -201,6 +203,7 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
.relayState(userSession.getId()); .relayState(userSession.getId());
if (getConfig().isWantAuthnRequestsSigned()) { if (getConfig().isWantAuthnRequestsSigned()) {
binding.signWith(realm.getPrivateKey(), realm.getPublicKey(), realm.getCertificate()) binding.signWith(realm.getPrivateKey(), realm.getPublicKey(), realm.getCertificate())
.signatureAlgorithm(getSignatureAlgorithm())
.signDocument(); .signDocument();
} }
return binding; return binding;
@ -250,4 +253,14 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
"</EntityDescriptor>\n"; "</EntityDescriptor>\n";
return Response.ok(descriptor, MediaType.APPLICATION_XML_TYPE).build(); return Response.ok(descriptor, MediaType.APPLICATION_XML_TYPE).build();
} }
public SignatureAlgorithm getSignatureAlgorithm() {
String alg = getConfig().getSignatureAlgorithm();
if (alg != null) {
SignatureAlgorithm algorithm = SignatureAlgorithm.valueOf(alg);
if (algorithm != null) return algorithm;
}
return SignatureAlgorithm.RSA_SHA256;
}
} }

View file

@ -87,6 +87,14 @@ public class SAMLIdentityProviderConfig extends IdentityProviderModel {
getConfig().put("wantAuthnRequestsSigned", String.valueOf(wantAuthnRequestsSigned)); getConfig().put("wantAuthnRequestsSigned", String.valueOf(wantAuthnRequestsSigned));
} }
public String getSignatureAlgorithm() {
return getConfig().get("signatureAlgorithm");
}
public void setSignatureAlgorithm(String signatureAlgorithm) {
getConfig().put("signatureAlgorithm", signatureAlgorithm);
}
public String getEncryptionPublicKey() { public String getEncryptionPublicKey() {
return getConfig().get("encryptionPublicKey"); return getConfig().get("encryptionPublicKey");
} }

View file

@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd">
<changeSet author="mposolda@redhat.com" id="1.6.0">
<addColumn tableName="KEYCLOAK_ROLE">
<column name="SCOPE_PARAM_REQUIRED" type="BOOLEAN" defaultValueBoolean="false">
<constraints nullable="false"/>
</column>
</addColumn>
<createTable tableName="OFFLINE_USER_SESSION">
<column name="USER_ID" type="VARCHAR(36)">
<constraints nullable="false"/>
</column>
<column name="USER_SESSION_ID" type="VARCHAR(36)">
<constraints nullable="false"/>
</column>
<column name="DATA" type="CLOB"/>
</createTable>
<createTable tableName="OFFLINE_CLIENT_SESSION">
<column name="USER_ID" type="VARCHAR(36)">
<constraints nullable="false"/>
</column>
<column name="CLIENT_SESSION_ID" type="VARCHAR(36)">
<constraints nullable="false"/>
</column>
<column name="USER_SESSION_ID" type="VARCHAR(36)">
<constraints nullable="false"/>
</column>
<column name="CLIENT_ID" type="VARCHAR(36)">
<constraints nullable="false"/>
</column>
<column name="DATA" type="CLOB"/>
</createTable>
<addPrimaryKey columnNames="USER_SESSION_ID" constraintName="CONSTRAINT_OFFLINE_US_SES_PK" tableName="OFFLINE_USER_SESSION"/>
<addPrimaryKey columnNames="CLIENT_SESSION_ID" constraintName="CONSTRAINT_OFFLINE_CL_SES_PK" tableName="OFFLINE_CLIENT_SESSION"/>
<addForeignKeyConstraint baseColumnNames="USER_ID" baseTableName="OFFLINE_USER_SESSION" constraintName="FK_OFFLINE_US_SES_USER" referencedColumnNames="ID" referencedTableName="USER_ENTITY"/>
<addForeignKeyConstraint baseColumnNames="USER_ID" baseTableName="OFFLINE_CLIENT_SESSION" constraintName="FK_OFFLINE_CL_SES_USER" referencedColumnNames="ID" referencedTableName="USER_ENTITY"/>
<addForeignKeyConstraint baseColumnNames="USER_SESSION_ID" baseTableName="OFFLINE_CLIENT_SESSION" constraintName="FK_OFFLINE_CL_US_SES" referencedColumnNames="USER_SESSION_ID" referencedTableName="OFFLINE_USER_SESSION"/>
</changeSet>
</databaseChangeLog>

View file

@ -9,4 +9,5 @@
<include file="META-INF/jpa-changelog-1.3.0.xml"/> <include file="META-INF/jpa-changelog-1.3.0.xml"/>
<include file="META-INF/jpa-changelog-1.4.0.xml"/> <include file="META-INF/jpa-changelog-1.4.0.xml"/>
<include file="META-INF/jpa-changelog-1.5.0.xml"/> <include file="META-INF/jpa-changelog-1.5.0.xml"/>
<include file="META-INF/jpa-changelog-1.6.0.xml"/>
</databaseChangeLog> </databaseChangeLog>

View file

@ -24,7 +24,7 @@
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.hibernate.javax.persistence</groupId> <groupId>org.hibernate.javax.persistence</groupId>
<artifactId>hibernate-jpa-2.1-api</artifactId> <artifactId>${hibernate.javax.persistence.artifactId}</artifactId>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>

View file

@ -29,6 +29,8 @@
<class>org.keycloak.models.jpa.entities.AuthenticationExecutionEntity</class> <class>org.keycloak.models.jpa.entities.AuthenticationExecutionEntity</class>
<class>org.keycloak.models.jpa.entities.AuthenticatorConfigEntity</class> <class>org.keycloak.models.jpa.entities.AuthenticatorConfigEntity</class>
<class>org.keycloak.models.jpa.entities.RequiredActionProviderEntity</class> <class>org.keycloak.models.jpa.entities.RequiredActionProviderEntity</class>
<class>org.keycloak.models.jpa.entities.OfflineUserSessionEntity</class>
<class>org.keycloak.models.jpa.entities.OfflineClientSessionEntity</class>
<!-- JpaAuditProviders --> <!-- JpaAuditProviders -->
<class>org.keycloak.events.jpa.EventEntity</class> <class>org.keycloak.events.jpa.EventEntity</class>

View file

@ -48,6 +48,8 @@ public class DefaultMongoConnectionFactoryProvider implements MongoConnectionPro
"org.keycloak.models.entities.AuthenticationFlowEntity", "org.keycloak.models.entities.AuthenticationFlowEntity",
"org.keycloak.models.entities.AuthenticatorConfigEntity", "org.keycloak.models.entities.AuthenticatorConfigEntity",
"org.keycloak.models.entities.RequiredActionProviderEntity", "org.keycloak.models.entities.RequiredActionProviderEntity",
"org.keycloak.models.entities.OfflineUserSessionEntity",
"org.keycloak.models.entities.OfflineClientSessionEntity",
}; };
private static final Logger logger = Logger.getLogger(DefaultMongoConnectionFactoryProvider.class); private static final Logger logger = Logger.getLogger(DefaultMongoConnectionFactoryProvider.class);

View file

@ -29,27 +29,22 @@
<dependency> <dependency>
<groupId>org.bouncycastle</groupId> <groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId> <artifactId>bcprov-jdk15on</artifactId>
<scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.bouncycastle</groupId> <groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk15on</artifactId> <artifactId>bcpkix-jdk15on</artifactId>
<scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>net.iharder</groupId> <groupId>net.iharder</groupId>
<artifactId>base64</artifactId> <artifactId>base64</artifactId>
<scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.codehaus.jackson</groupId> <groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-core-asl</artifactId> <artifactId>jackson-core-asl</artifactId>
<scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.codehaus.jackson</groupId> <groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-mapper-asl</artifactId> <artifactId>jackson-mapper-asl</artifactId>
<scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>junit</groupId> <groupId>junit</groupId>

View file

@ -38,6 +38,9 @@ public interface OAuth2Constants {
// https://tools.ietf.org/html/draft-jones-oauth-jwt-bearer-03#section-2.2 // https://tools.ietf.org/html/draft-jones-oauth-jwt-bearer-03#section-2.2
String CLIENT_ASSERTION_TYPE_JWT = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"; String CLIENT_ASSERTION_TYPE_JWT = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer";
// http://openid.net/specs/openid-connect-core-1_0.html#OfflineAccess
String OFFLINE_ACCESS = "offline_access";
} }

View file

@ -1,6 +1,7 @@
package org.keycloak.representations; package org.keycloak.representations;
import org.codehaus.jackson.annotate.JsonProperty; import org.codehaus.jackson.annotate.JsonProperty;
import org.keycloak.util.RefreshTokenUtil;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@ -11,9 +12,8 @@ import java.util.Map;
*/ */
public class RefreshToken extends AccessToken { public class RefreshToken extends AccessToken {
private RefreshToken() { private RefreshToken() {
type("REFRESH"); type(RefreshTokenUtil.TOKEN_TYPE_REFRESH);
} }
/** /**

View file

@ -8,7 +8,6 @@ import java.util.Comparator;
* @version $Revision: 1 $ * @version $Revision: 1 $
*/ */
public class AuthenticationExecutionRepresentation implements Serializable { public class AuthenticationExecutionRepresentation implements Serializable {
private static final long serialVersionUID = 1L;
private String authenticatorConfig; private String authenticatorConfig;
private String authenticator; private String authenticator;

View file

@ -8,7 +8,6 @@ import java.util.List;
* @version $Revision: 1 $ * @version $Revision: 1 $
*/ */
public class AuthenticationFlowRepresentation implements Serializable { public class AuthenticationFlowRepresentation implements Serializable {
private static final long serialVersionUID = 1L;
private String alias; private String alias;
private String description; private String description;

View file

@ -9,7 +9,6 @@ import java.util.Map;
* @version $Revision: 1 $ * @version $Revision: 1 $
*/ */
public class AuthenticatorConfigRepresentation implements Serializable { public class AuthenticatorConfigRepresentation implements Serializable {
private static final long serialVersionUID = 1L;
private String alias; private String alias;
private Map<String, String> config = new HashMap<String, String>(); private Map<String, String> config = new HashMap<String, String>();

View file

@ -12,6 +12,7 @@ public class RoleRepresentation {
protected String id; protected String id;
protected String name; protected String name;
protected String description; protected String description;
protected Boolean scopeParamRequired;
protected boolean composite; protected boolean composite;
protected Composites composites; protected Composites composites;
@ -46,9 +47,10 @@ public class RoleRepresentation {
public RoleRepresentation() { public RoleRepresentation() {
} }
public RoleRepresentation(String name, String description) { public RoleRepresentation(String name, String description, boolean scopeParamRequired) {
this.name = name; this.name = name;
this.description = description; this.description = description;
this.scopeParamRequired = scopeParamRequired;
} }
public String getId() { public String getId() {
@ -75,6 +77,14 @@ public class RoleRepresentation {
this.description = description; this.description = description;
} }
public Boolean isScopeParamRequired() {
return scopeParamRequired;
}
public void setScopeParamRequired(Boolean scopeParamRequired) {
this.scopeParamRequired = scopeParamRequired;
}
public Composites getComposites() { public Composites getComposites() {
return composites; return composites;
} }

View file

@ -0,0 +1,62 @@
package org.keycloak.util;
import java.io.IOException;
import org.keycloak.OAuth2Constants;
import org.keycloak.representations.RefreshToken;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class RefreshTokenUtil {
public static final String TOKEN_TYPE_REFRESH = "REFRESH";
public static final String TOKEN_TYPE_OFFLINE = "OFFLINE";
public static boolean isOfflineTokenRequested(String scopeParam) {
if (scopeParam == null) {
return false;
}
String[] scopes = scopeParam.split(" ");
for (String scope : scopes) {
if (OAuth2Constants.OFFLINE_ACCESS.equals(scope)) {
return true;
}
}
return false;
}
/**
* Return refresh token or offline otkne
*
* @param decodedToken
* @return
*/
public static RefreshToken getRefreshToken(byte[] decodedToken) throws IOException {
return JsonSerialization.readValue(decodedToken, RefreshToken.class);
}
private static RefreshToken getRefreshToken(String refreshToken) throws IOException {
byte[] decodedToken = Base64Url.decode(refreshToken);
return getRefreshToken(decodedToken);
}
/**
* Return true if given refreshToken represents offline token
*
* @param refreshToken
* @return
*/
public static boolean isOfflineToken(String refreshToken) {
try {
RefreshToken token = getRefreshToken(refreshToken);
return token.getType().equals(TOKEN_TYPE_OFFLINE);
} catch (IOException ioe) {
throw new RuntimeException(ioe);
}
}
}

View file

@ -14,10 +14,38 @@
<description/> <description/>
<dependencies> <dependencies>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-dependencies-server-all</artifactId>
<type>pom</type>
</dependency>
</dependencies> </dependencies>
<build> <build>
<finalName>keycloak-docs-${project.version}</finalName> <finalName>keycloak-docs-${project.version}</finalName>
<plugins> <plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<configuration>
<minmemory>128m</minmemory>
<maxmemory>1024m</maxmemory>
<dependencySourceIncludes>
<dependencySourceInclude>org.keycloak:*</dependencySourceInclude>
</dependencySourceIncludes>
<includeDependencySources>true</includeDependencySources>
<includeTransitiveDependencySources>true</includeTransitiveDependencySources>
</configuration>
<executions>
<execution>
<id>aggregate-javadoc</id>
<phase>compile</phase>
<goals>
<goal>javadoc</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-deploy-plugin</artifactId> <artifactId>maven-deploy-plugin</artifactId>

View file

@ -898,7 +898,7 @@ public class SecretQuestionRequiredActionFactory implements RequiredActionFactor
} }
]]></programlisting> ]]></programlisting>
where the <literal>mysecret</literal> needs to be replaced with the real value of client secret. You can obtain it from client admin console. where the <literal>mysecret</literal> needs to be replaced with the real value of client secret. You can obtain it from admin console from client configuration.
</para> </para>
</listitem> </listitem>
</varlistentry> </varlistentry>
@ -906,7 +906,7 @@ public class SecretQuestionRequiredActionFactory implements RequiredActionFactor
<term>Authentication with signed JWT</term> <term>Authentication with signed JWT</term>
<listitem> <listitem>
<para> <para>
This is based on the <ulink url="https://tools.ietf.org/html/draft-jones-oauth-jwt-bearer-03">JWT Bearer Token Profiles for OAuth 2.0</ulink> specification. This is based on the <ulink url="https://tools.ietf.org/html/rfc7523">JWT Bearer Token Profiles for OAuth 2.0</ulink> specification.
The client/adapter generates the <ulink url="https://tools.ietf.org/html/rfc7519">JWT</ulink> and signs it with his private key. The client/adapter generates the <ulink url="https://tools.ietf.org/html/rfc7519">JWT</ulink> and signs it with his private key.
The Keycloak then verifies the signed JWT with the client's public key and authenticates client based on it. The Keycloak then verifies the signed JWT with the client's public key and authenticates client based on it.
</para> </para>

View file

@ -20,6 +20,7 @@ public interface Details {
String REMEMBER_ME = "remember_me"; String REMEMBER_ME = "remember_me";
String TOKEN_ID = "token_id"; String TOKEN_ID = "token_id";
String REFRESH_TOKEN_ID = "refresh_token_id"; String REFRESH_TOKEN_ID = "refresh_token_id";
String REFRESH_TOKEN_TYPE = "refresh_token_type";
String VALIDATE_ACCESS_TOKEN = "validate_access_token"; String VALIDATE_ACCESS_TOKEN = "validate_access_token";
String UPDATED_REFRESH_TOKEN_ID = "updated_refresh_token_id"; String UPDATED_REFRESH_TOKEN_ID = "updated_refresh_token_id";
String NODE_HOST = "node_host"; String NODE_HOST = "node_host";

View file

@ -35,7 +35,7 @@
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.hibernate.javax.persistence</groupId> <groupId>org.hibernate.javax.persistence</groupId>
<artifactId>hibernate-jpa-2.1-api</artifactId> <artifactId>${hibernate.javax.persistence.artifactId}</artifactId>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>

View file

@ -4,7 +4,6 @@ import javax.xml.ws.WebFault;
@WebFault(name = "UnknownProductFault") @WebFault(name = "UnknownProductFault")
public class UnknownProductFault extends Exception { public class UnknownProductFault extends Exception {
public static final long serialVersionUID = 20081110144906L;
private org.keycloak.example.ws.types.UnknownProductFault unknownProductFault; private org.keycloak.example.ws.types.UnknownProductFault unknownProductFault;

View file

@ -1,5 +1,6 @@
package org.keycloak.account.freemarker.model; package org.keycloak.account.freemarker.model;
import java.util.ArrayList;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
@ -11,6 +12,7 @@ import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel; import org.keycloak.models.RoleModel;
import org.keycloak.models.UserModel; import org.keycloak.models.UserModel;
import org.keycloak.protocol.oidc.TokenManager; import org.keycloak.protocol.oidc.TokenManager;
import org.keycloak.services.offline.OfflineTokenUtils;
import org.keycloak.util.MultivaluedHashMap; import org.keycloak.util.MultivaluedHashMap;
/** /**
@ -21,6 +23,9 @@ public class ApplicationsBean {
private List<ApplicationEntry> applications = new LinkedList<ApplicationEntry>(); private List<ApplicationEntry> applications = new LinkedList<ApplicationEntry>();
public ApplicationsBean(RealmModel realm, UserModel user) { public ApplicationsBean(RealmModel realm, UserModel user) {
Set<ClientModel> offlineClients = OfflineTokenUtils.findClientsWithOfflineToken(realm, user);
List<ClientModel> realmClients = realm.getClients(); List<ClientModel> realmClients = realm.getClients();
for (ClientModel client : realmClients) { for (ClientModel client : realmClients) {
// Don't show bearerOnly clients // Don't show bearerOnly clients
@ -28,7 +33,7 @@ public class ApplicationsBean {
continue; continue;
} }
Set<RoleModel> availableRoles = TokenManager.getAccess(null, client, user); Set<RoleModel> availableRoles = TokenManager.getAccess(null, false, client, user);
// Don't show applications, which user doesn't have access into (any available roles) // Don't show applications, which user doesn't have access into (any available roles)
if (availableRoles.isEmpty()) { if (availableRoles.isEmpty()) {
continue; continue;
@ -52,7 +57,13 @@ public class ApplicationsBean {
} }
} }
ApplicationEntry appEntry = new ApplicationEntry(realmRolesAvailable, resourceRolesAvailable, realmRolesGranted, resourceRolesGranted, client, claimsGranted); List<String> additionalGrants = new ArrayList<>();
if (offlineClients.contains(client)) {
additionalGrants.add("${offlineToken}");
}
ApplicationEntry appEntry = new ApplicationEntry(realmRolesAvailable, resourceRolesAvailable, realmRolesGranted, resourceRolesGranted, client,
claimsGranted, additionalGrants);
applications.add(appEntry); applications.add(appEntry);
} }
} }
@ -82,16 +93,18 @@ public class ApplicationsBean {
private final MultivaluedHashMap<String, ClientRoleEntry> resourceRolesGranted; private final MultivaluedHashMap<String, ClientRoleEntry> resourceRolesGranted;
private final ClientModel client; private final ClientModel client;
private final List<String> claimsGranted; private final List<String> claimsGranted;
private final List<String> additionalGrants;
public ApplicationEntry(List<RoleModel> realmRolesAvailable, MultivaluedHashMap<String, ClientRoleEntry> resourceRolesAvailable, public ApplicationEntry(List<RoleModel> realmRolesAvailable, MultivaluedHashMap<String, ClientRoleEntry> resourceRolesAvailable,
List<RoleModel> realmRolesGranted, MultivaluedHashMap<String, ClientRoleEntry> resourceRolesGranted, List<RoleModel> realmRolesGranted, MultivaluedHashMap<String, ClientRoleEntry> resourceRolesGranted,
ClientModel client, List<String> claimsGranted) { ClientModel client, List<String> claimsGranted, List<String> additionalGrants) {
this.realmRolesAvailable = realmRolesAvailable; this.realmRolesAvailable = realmRolesAvailable;
this.resourceRolesAvailable = resourceRolesAvailable; this.resourceRolesAvailable = resourceRolesAvailable;
this.realmRolesGranted = realmRolesGranted; this.realmRolesGranted = realmRolesGranted;
this.resourceRolesGranted = resourceRolesGranted; this.resourceRolesGranted = resourceRolesGranted;
this.client = client; this.client = client;
this.claimsGranted = claimsGranted; this.claimsGranted = claimsGranted;
this.additionalGrants = additionalGrants;
} }
public List<RoleModel> getRealmRolesAvailable() { public List<RoleModel> getRealmRolesAvailable() {
@ -118,6 +131,9 @@ public class ApplicationsBean {
return claimsGranted; return claimsGranted;
} }
public List<String> getAdditionalGrants() {
return additionalGrants;
}
} }
// Same class used in OAuthGrantBean as well. Maybe should be merged into common-freemarker... // Same class used in OAuthGrantBean as well. Maybe should be merged into common-freemarker...

View file

@ -18,6 +18,7 @@
<td>${msg("availablePermissions")}</td> <td>${msg("availablePermissions")}</td>
<td>${msg("grantedPermissions")}</td> <td>${msg("grantedPermissions")}</td>
<td>${msg("grantedPersonalInfo")}</td> <td>${msg("grantedPersonalInfo")}</td>
<td>${msg("additionalGrants")}</td>
<td>${msg("action")}</td> <td>${msg("action")}</td>
</tr> </tr>
</thead> </thead>
@ -76,7 +77,13 @@
</td> </td>
<td> <td>
<#if application.client.consentRequired && application.claimsGranted?has_content> <#list application.additionalGrants as grant>
${advancedMsg(grant)}<#if grant_has_next>, </#if>
</#list>
</td>
<td>
<#if (application.client.consentRequired && application.claimsGranted?has_content) || application.additionalGrants?has_content>
<button type='submit' class='${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!}' id='revoke-${application.client.clientId}' name='clientId' value="${application.client.id}">${msg("revoke")}</button> <button type='submit' class='${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!}' id='revoke-${application.client.clientId}' name='clientId' value="${application.client.id}">${msg("revoke")}</button>
</#if> </#if>
</td> </td>

View file

@ -52,6 +52,7 @@ role_manage-events=Manage events
role_view-profile=View profile role_view-profile=View profile
role_manage-account=Manage account role_manage-account=Manage account
role_read-token=Read token role_read-token=Read token
role_offline-access=Offline access
client_account=Account client_account=Account
client_security-admin-console=Security Admin Console client_security-admin-console=Security Admin Console
client_realm-management=Realm Management client_realm-management=Realm Management
@ -85,9 +86,11 @@ application=Application
availablePermissions=Available Permissions availablePermissions=Available Permissions
grantedPermissions=Granted Permissions grantedPermissions=Granted Permissions
grantedPersonalInfo=Granted Personal Info grantedPersonalInfo=Granted Personal Info
additionalGrants=Additional Grants
action=Action action=Action
inResource=in inResource=in
fullAccess=Full Access fullAccess=Full Access
offlineToken=Offline Token
revoke=Revoke Grant revoke=Revoke Grant
configureAuthenticators=Configured Authenticators configureAuthenticators=Configured Authenticators

View file

@ -2036,3 +2036,26 @@ module.directive( 'kcOpen', function ( $location ) {
}); });
}; };
}); });
module.directive('kcOnReadFile', function ($parse) {
console.debug('kcOnReadFile');
return {
restrict: 'A',
scope: false,
link: function(scope, element, attrs) {
var fn = $parse(attrs.kcOnReadFile);
element.on('change', function(onChangeEvent) {
var reader = new FileReader();
reader.onload = function(onLoadEvent) {
scope.$apply(function() {
fn(scope, {$fileContent:onLoadEvent.target.result});
});
};
reader.readAsText((onChangeEvent.srcElement || onChangeEvent.target).files[0]);
});
}
};
});

View file

@ -155,7 +155,7 @@ module.controller('RealmDropdownCtrl', function($scope, Realm, Current, Auth, $l
} }
}); });
module.controller('RealmCreateCtrl', function($scope, Current, Realm, $upload, $http, WhoAmI, $location, Dialog, Notifications, Auth) { module.controller('RealmCreateCtrl', function($scope, Current, Realm, $upload, $http, WhoAmI, $location, $route, Dialog, Notifications, Auth, $modal) {
console.log('RealmCreateCtrl'); console.log('RealmCreateCtrl');
Current.realm = null; Current.realm = null;
@ -169,55 +169,21 @@ module.controller('RealmCreateCtrl', function($scope, Current, Realm, $upload, $
var oldCopy = angular.copy($scope.realm); var oldCopy = angular.copy($scope.realm);
$scope.onFileSelect = function($files) { $scope.importFile = function($fileContent){
$scope.files = $files; $scope.realm = angular.copy(JSON.parse($fileContent));
$scope.importing = true;
}; };
$scope.clearFileSelect = function() { $scope.viewImportDetails = function() {
$scope.files = null; $modal.open({
templateUrl: resourceUrl + '/partials/modal/view-object.html',
controller: 'ObjectModalCtrl',
resolve: {
object: function () {
return $scope.realm;
} }
$scope.uploadFile = function() {
//$files: an array of files selected, each file has name, size, and type.
for (var i = 0; i < $scope.files.length; i++) {
var $file = $scope.files[i];
$scope.upload = $upload.upload({
url: authUrl + '/admin/realms', //upload.php script, node.js route, or servlet url
// method: POST or PUT,
// headers: {'headerKey': 'headerValue'}, withCredential: true,
data: {myObj: ""},
file: $file
/* set file formData name for 'Content-Desposition' header. Default: 'file' */
//fileFormDataName: myFile,
/* customize how data is added to formData. See #40#issuecomment-28612000 for example */
//formDataAppender: function(formData, key, val){}
}).progress(function(evt) {
console.log('percent: ' + parseInt(100.0 * evt.loaded / evt.total));
}).success(function(data, status, headers) {
Realm.query(function(data) {
Current.realms = data;
WhoAmI.get(function(user) {
Auth.user = user;
Notifications.success("The realm has been uploaded.");
var location = headers('Location');
if (location) {
$location.url("/realms/" + location.substring(location.lastIndexOf('/') + 1));
} else {
$location.url("/realms");
} }
});
});
}) })
.error(function() {
Notifications.error("The realm can not be uploaded. Please verify the file.");
});
//.then(success, error, progress);
}
}; };
$scope.$watch('realm', function() { $scope.$watch('realm', function() {
@ -226,6 +192,12 @@ module.controller('RealmCreateCtrl', function($scope, Current, Realm, $upload, $
} }
}, true); }, true);
$scope.$watch('realm.realm', function() {
if (create) {
$scope.realm.id = $scope.realm.realm;
}
}, true);
$scope.save = function() { $scope.save = function() {
var realmCopy = angular.copy($scope.realm); var realmCopy = angular.copy($scope.realm);
Realm.create(realmCopy, function() { Realm.create(realmCopy, function() {
@ -243,10 +215,17 @@ module.controller('RealmCreateCtrl', function($scope, Current, Realm, $upload, $
}; };
$scope.cancel = function() { $scope.cancel = function() {
window.history.back(); $location.url("/");
}; };
$scope.reset = function() {
$route.reload();
}
}); });
module.controller('ObjectModalCtrl', function($scope, object) {
$scope.object = object;
});
module.controller('RealmDetailCtrl', function($scope, Current, Realm, realm, serverInfo, $http, $location, Dialog, Notifications, WhoAmI, Auth) { module.controller('RealmDetailCtrl', function($scope, Current, Realm, realm, serverInfo, $http, $location, Dialog, Notifications, WhoAmI, Auth) {
$scope.createRealm = !realm.realm; $scope.createRealm = !realm.realm;
@ -693,10 +672,17 @@ module.controller('RealmIdentityProviderCtrl', function($scope, $filter, $upload
} }
]; ];
$scope.signatureAlgorithms = [
"RSA_SHA1",
"RSA_SHA256",
"RSA_SHA512",
"DSA_SHA1"
];
if (instance && instance.alias) { if (instance && instance.alias) {
} else { } else {
$scope.identityProvider.config.nameIDPolicyFormat = $scope.nameIdFormats[0].format; $scope.identityProvider.config.nameIDPolicyFormat = $scope.nameIdFormats[0].format;
$scope.identityProvider.config.signatureAlgorithm = $scope.signatureAlgorithms[1];
$scope.identityProvider.updateProfileFirstLoginMode = "off"; $scope.identityProvider.updateProfileFirstLoginMode = "off";
} }
} }

View file

@ -32,6 +32,13 @@
required> --> required> -->
</div> </div>
</div> </div>
<div class="form-group">
<label class="col-md-2 control-label" for="scopeParamRequired">Scope Param Required </label>
<kc-tooltip>This role will be granted just if scope parameter with role name is used during authorization/token request.</kc-tooltip>
<div class="col-md-6">
<input ng-model="role.scopeParamRequired" name="scopeParamRequired" id="scopeParamRequired" onoffswitch />
</div>
</div>
<div class="form-group clearfix block" data-ng-hide="create"> <div class="form-group clearfix block" data-ng-hide="create">
<label class="col-md-2 control-label" for="compositeSwitch" class="control-label">Composite Roles</label> <label class="col-md-2 control-label" for="compositeSwitch" class="control-label">Composite Roles</label>
<div class="col-md-6"> <div class="col-md-6">

View file

@ -0,0 +1,3 @@
<div style="padding: 20px 20px 10px 20px">
<pre ng-bind = "{{object}} | json"></pre>
</div>

View file

@ -34,7 +34,7 @@
placeholder="No value assigned" min="1" required> placeholder="No value assigned" min="1" required>
</td> </td>
<td class="kc-action-cell"> <td class="kc-action-cell">
<button class="btn btn-default btn-block btn-sm" ng-click="removePolicy($index)">Delete</button> <button type="button" class="btn btn-default btn-block btn-sm" ng-click="removePolicy($index)">Delete</button>
</td> </td>
</tr> </tr>
</tbody> </tbody>

View file

@ -1,29 +1,22 @@
<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2"> <div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
<form class="form-horizontal" name="realmForm" novalidate>
<fieldset>
<legend><span class="text">Import Realm</span></legend>
<div class="form-group">
<label for="import-file" class="col-sm-2 control-label">Import JSON File </label>
<div class="col-md-6">
<div class="controls kc-button-input-file" data-ng-show="!files || files.length == 0">
<label for="import-file" class="btn btn-default">Select file <i class="pficon pficon-import"></i></label>
<input id="import-file" type="file" class="hidden" ng-file-select="onFileSelect($files)">
</div>
<span class="kc-uploaded-file" data-ng-show="files.length > 0">{{files[0].name}}</span>
</div>
</div>
<div class="form-group">
<div class="col-md-10 col-md-offset-2">
<button type="submit" data-ng-disabled="files.length == 0" data-ng-click="uploadFile()" class="btn btn-primary">Upload</button>
<button type="submit" data-ng-disabled="files.length == 0" data-ng-click="clearFileSelect()" class="btn btn-default">Cancel</button>
</div>
</div>
</fieldset>
</form>
<form class="form-horizontal" name="realmForm" novalidate> <form class="form-horizontal" name="realmForm" novalidate>
<fieldset> <fieldset>
<legend><span class="text">Create Realm</span></legend> <legend><span class="text">Create Realm</span></legend>
<div class="form-group">
<label for="name" class="col-sm-2 control-label">Import</label>
<div class="col-md-6" data-ng-hide="importing">
<label for="import-file" class="btn btn-default">Select file <i class="pficon pficon-import"></i></label>
<input id="import-file" type="file" class="hidden" kc-on-read-file="importFile($fileContent)">
</div>
<div class="col-md-6" data-ng-show="importing">
<button class="btn btn-default" data-ng-click="viewImportDetails()">View details</button>
<button class="btn btn-default" data-ng-click="reset()">Clear import</button>
</div>
</div>
<div class="form-group"> <div class="form-group">
<label for="name" class="col-sm-2 control-label">Name <span class="required">*</span></label> <label for="name" class="col-sm-2 control-label">Name <span class="required">*</span></label>
@ -42,6 +35,7 @@
<div class="form-group"> <div class="form-group">
<div class="col-md-10 col-md-offset-2"> <div class="col-md-10 col-md-offset-2">
<button kc-save data-ng-disabled="!changed">Create</button> <button kc-save data-ng-disabled="!changed">Create</button>
<button kc-cancel data-ng-click="cancel()">Cancel</button>
</div> </div>
</div> </div>
</form> </form>

View file

@ -135,6 +135,18 @@
</div> </div>
<kc-tooltip> Indicates whether the identity provider expects signed a AuthnRequest.</kc-tooltip> <kc-tooltip> Indicates whether the identity provider expects signed a AuthnRequest.</kc-tooltip>
</div> </div>
<div class="form-group" data-ng-show="identityProvider.config.wantAuthnRequestsSigned == 'true'">
<label class="col-md-2 control-label" for="signatureAlgorithm">Signature Algorithm</label>
<div class="col-sm-6">
<div>
<select class="form-control" id="signatureAlgorithm"
ng-model="identityProvider.config.signatureAlgorithm"
ng-options="alg for alg in signatureAlgorithms">
</select>
</div>
</div>
<kc-tooltip>The signature algorithm to use to sign documents.</kc-tooltip>
</div>
<div class="form-group"> <div class="form-group">
<label class="col-md-2 control-label" for="forceAuthn">Force Authentication</label> <label class="col-md-2 control-label" for="forceAuthn">Force Authentication</label>
<div class="col-md-6"> <div class="col-md-6">

View file

@ -28,6 +28,13 @@
<textarea class="form-control" rows="5" cols="50" id="description" name="description" data-ng-model="role.description"></textarea> <textarea class="form-control" rows="5" cols="50" id="description" name="description" data-ng-model="role.description"></textarea>
</div> </div>
</div> </div>
<div class="form-group">
<label class="col-md-2 control-label" for="scopeParamRequired">Scope Param Required </label>
<kc-tooltip>This role will be granted just if scope parameter with role name is used during authorization/token request.</kc-tooltip>
<div class="col-md-6">
<input ng-model="role.scopeParamRequired" name="scopeParamRequired" id="scopeParamRequired" onoffswitch />
</div>
</div>
<div class="form-group" data-ng-hide="create"> <div class="form-group" data-ng-hide="create">
<label class="col-md-2 control-label" for="compositeSwitch" class="control-label">Composite Roles</label> <label class="col-md-2 control-label" for="compositeSwitch" class="control-label">Composite Roles</label>
<div class="col-md-6"> <div class="col-md-6">

View file

@ -6,6 +6,16 @@
${msg("loginProfileTitle")} ${msg("loginProfileTitle")}
<#elseif section = "form"> <#elseif section = "form">
<form id="kc-update-profile-form" class="${properties.kcFormClass!}" action="${url.loginAction}" method="post"> <form id="kc-update-profile-form" class="${properties.kcFormClass!}" action="${url.loginAction}" method="post">
<#if realm.editUsernameAllowed>
<div class="${properties.kcFormGroupClass!} ${messagesPerField.printIfExists('username',properties.kcFormGroupErrorClass!)}">
<div class="${properties.kcLabelWrapperClass!}">
<label for="username" class="${properties.kcLabelClass!}">${msg("username")}</label>
</div>
<div class="${properties.kcInputWrapperClass!}">
<input type="text" id="username" name="username" value="${(user.username!'')?html}" class="${properties.kcInputClass!}"/>
</div>
</div>
</#if>
<div class="${properties.kcFormGroupClass!} ${messagesPerField.printIfExists('email',properties.kcFormGroupErrorClass!)}"> <div class="${properties.kcFormGroupClass!} ${messagesPerField.printIfExists('email',properties.kcFormGroupErrorClass!)}">
<div class="${properties.kcLabelWrapperClass!}"> <div class="${properties.kcLabelWrapperClass!}">
<label for="email" class="${properties.kcLabelClass!}">${msg("email")}</label> <label for="email" class="${properties.kcLabelClass!}">${msg("email")}</label>

View file

@ -104,6 +104,7 @@ role_manage-events=Manage events
role_view-profile=View profile role_view-profile=View profile
role_manage-account=Manage account role_manage-account=Manage account
role_read-token=Read token role_read-token=Read token
role_offline-access=Offline access
client_account=Account client_account=Account
client_security-admin-console=Security Admin Console client_security-admin-console=Security Admin Console
client_realm-management=Realm Management client_realm-management=Realm Management

View file

@ -70,6 +70,8 @@ public class ProfileBean {
} }
public String getUsername() { return formData != null ? formData.getFirst("username") : user.getUsername(); }
public String getFirstName() { public String getFirstName() {
return formData != null ? formData.getFirst("firstName") : user.getFirstName(); return formData != null ? formData.getFirst("firstName") : user.getFirstName();
} }

View file

@ -66,6 +66,10 @@ public class RealmBean {
return realm.isInternationalizationEnabled(); return realm.isInternationalizationEnabled();
} }
public boolean isEditUsernameAllowed() {
return realm.isEditUsernameAllowed();
}
public boolean isPassword() { public boolean isPassword() {
for (RequiredCredentialModel r : realm.getRequiredCredentials()) { for (RequiredCredentialModel r : realm.getRequiredCredentials()) {
if (r.getType().equals(CredentialRepresentation.PASSWORD)) { if (r.getType().equals(CredentialRepresentation.PASSWORD)) {

View file

@ -134,6 +134,9 @@ public class OAuthRequestAuthenticator {
String idpHint = getQueryParamValue(AdapterConstants.KC_IDP_HINT); String idpHint = getQueryParamValue(AdapterConstants.KC_IDP_HINT);
url = UriUtils.stripQueryParam(url, AdapterConstants.KC_IDP_HINT); url = UriUtils.stripQueryParam(url, AdapterConstants.KC_IDP_HINT);
String scope = getQueryParamValue(OAuth2Constants.SCOPE);
url = UriUtils.stripQueryParam(url, OAuth2Constants.SCOPE);
KeycloakUriBuilder redirectUriBuilder = deployment.getAuthUrl().clone() KeycloakUriBuilder redirectUriBuilder = deployment.getAuthUrl().clone()
.queryParam(OAuth2Constants.RESPONSE_TYPE, OAuth2Constants.CODE) .queryParam(OAuth2Constants.RESPONSE_TYPE, OAuth2Constants.CODE)
.queryParam(OAuth2Constants.CLIENT_ID, deployment.getResourceName()) .queryParam(OAuth2Constants.CLIENT_ID, deployment.getResourceName())
@ -146,6 +149,9 @@ public class OAuthRequestAuthenticator {
if (idpHint != null && idpHint.length() > 0) { if (idpHint != null && idpHint.length() > 0) {
redirectUriBuilder.queryParam(AdapterConstants.KC_IDP_HINT,idpHint); redirectUriBuilder.queryParam(AdapterConstants.KC_IDP_HINT,idpHint);
} }
if (scope != null && scope.length() > 0) {
redirectUriBuilder.queryParam(OAuth2Constants.SCOPE, scope);
}
return redirectUriBuilder.build().toString(); return redirectUriBuilder.build().toString();
} }

View file

@ -117,7 +117,12 @@ public class RefreshableKeycloakSecurityContext extends KeycloakSecurityContext
} }
this.token = token; this.token = token;
if (response.getRefreshToken() != null) {
if (log.isTraceEnabled()) {
log.trace("Setup new refresh token to the security context");
}
this.refreshToken = response.getRefreshToken(); this.refreshToken = response.getRefreshToken();
}
this.tokenString = tokenString; this.tokenString = tokenString;
tokenStore.refreshCallback(this); tokenStore.refreshCallback(this);
return true; return true;

View file

@ -14,7 +14,7 @@ import org.keycloak.adapters.KeycloakDeployment;
* *
* You must specify a file * You must specify a file
* META-INF/services/org.keycloak.adapters.authentication.ClientCredentialsProvider in the WAR that this class is contained in (or in the JAR that is attached to the WEB-INF/lib or as jboss module * META-INF/services/org.keycloak.adapters.authentication.ClientCredentialsProvider in the WAR that this class is contained in (or in the JAR that is attached to the WEB-INF/lib or as jboss module
* if you want to share the implementation among more WARs). This file must have the fully qualified class name of all your ClientAuthenticatorFactory classes * if you want to share the implementation among more WARs).
* *
* NOTE: The SPI is not finished and method signatures are still subject to change in future versions (for example to support * NOTE: The SPI is not finished and method signatures are still subject to change in future versions (for example to support
* authentication with client certificate) * authentication with client certificate)

View file

@ -13,7 +13,7 @@ import org.keycloak.util.Time;
/** /**
* Client authentication based on JWT signed by client private key . * Client authentication based on JWT signed by client private key .
* See <a href="https://tools.ietf.org/html/draft-jones-oauth-jwt-bearer-03">specs</a> for more details. * See <a href="https://tools.ietf.org/html/rfc7519">specs</a> for more details.
* *
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a> * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/ */

View file

@ -8,7 +8,6 @@ import java.security.Principal;
*/ */
public class RolePrincipal implements Principal, Serializable { public class RolePrincipal implements Principal, Serializable {
private static final long serialVersionUID = -5538962177019315447L;
private String roleName = null; private String roleName = null;
public RolePrincipal(String roleName) { public RolePrincipal(String roleName) {

View file

@ -164,6 +164,10 @@
url += '&kc_idp_hint=' + options.idpHint; url += '&kc_idp_hint=' + options.idpHint;
} }
if (options && options.scope) {
url += '&scope=' + options.scope;
}
return url; return url;
} }

View file

@ -8,7 +8,6 @@ import java.util.HashSet;
import java.util.Set; import java.util.Set;
public class SimpleGroup extends SimplePrincipal implements Group { public class SimpleGroup extends SimplePrincipal implements Group {
private static final long serialVersionUID = 3273437693505893786L;
private final Set<Principal> members = new HashSet<Principal>(); private final Set<Principal> members = new HashSet<Principal>();
/** /**

View file

@ -14,7 +14,6 @@ import java.security.Principal;
public class SimplePrincipal implements Principal, Serializable { public class SimplePrincipal implements Principal, Serializable {
/** SimplePrincipal.java */ /** SimplePrincipal.java */
private static final long serialVersionUID = -5645357206342793145L;
/** The unique identifier for this principal. */ /** The unique identifier for this principal. */
private final String name; private final String name;

View file

@ -7,31 +7,21 @@
## Release ## Release
*Releasing currently requires using JDK 7 due to a bug in JAX-RS Doclets*
### Clone from GitHub ### Clone from GitHub
# git clone https://github.com/keycloak/keycloak.git # git clone https://github.com/keycloak/keycloak.git
# cd keycloak # cd keycloak
### Update version ### Prepare the release
# mvn versions:set -DnewVersion=$VERSION -DgenerateBackupPoms=false -Pjboss-release # mvn -Pjboss-release release:prepare
### Build ### Perform the release
# mvn install install -Pdistribution # mvn -Pjboss-release release:perform
# mvn install -Pjboss-release -DskipTests
### Tag
# git tag $VERSION
# git push --tags
### Deploy to Nexus ### Deploy to Nexus
# mvn deploy -DskipTests -Pjboss-release
Then login to Nexus and release the maven uploads in the staging area. Artifacts will eventually be synced to Maven Central, but this can take up to 24 hours. Then login to Nexus and release the maven uploads in the staging area. Artifacts will eventually be synced to Maven Central, but this can take up to 24 hours.
### Upload ### Upload
@ -51,7 +41,6 @@ Upload all artifacts to downloads.jboss.org (see https://mojo.redhat.com/docs/DO
# git tag $VERSION # git tag $VERSION
# git push --tags # git push --tags
## After Release ## After Release
### Update Bower ### Update Bower

View file

@ -11,7 +11,7 @@ public interface MigrationModel {
/** /**
* Must have the form of major.minor.micro as the version is parsed and numbers are compared * Must have the form of major.minor.micro as the version is parsed and numbers are compared
*/ */
public static final String LATEST_VERSION = "1.5.0"; public static final String LATEST_VERSION = "1.6.0";
String getStoredVersion(); String getStoredVersion();
void setStoredVersion(String version); void setStoredVersion(String version);

View file

@ -4,6 +4,7 @@ import org.jboss.logging.Logger;
import org.keycloak.migration.migrators.MigrateTo1_3_0; import org.keycloak.migration.migrators.MigrateTo1_3_0;
import org.keycloak.migration.migrators.MigrateTo1_4_0; import org.keycloak.migration.migrators.MigrateTo1_4_0;
import org.keycloak.migration.migrators.MigrateTo1_5_0; import org.keycloak.migration.migrators.MigrateTo1_5_0;
import org.keycloak.migration.migrators.MigrateTo1_6_0;
import org.keycloak.migration.migrators.MigrationTo1_2_0_CR1; import org.keycloak.migration.migrators.MigrationTo1_2_0_CR1;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
@ -47,6 +48,12 @@ public class MigrationModelManager {
} }
new MigrateTo1_5_0().migrate(session); new MigrateTo1_5_0().migrate(session);
} }
if (stored == null || stored.lessThan(MigrateTo1_6_0.VERSION)) {
if (stored != null) {
logger.debug("Migrating older model to 1.6.0 updates");
}
new MigrateTo1_6_0().migrate(session);
}
model.setStoredVersion(MigrationModel.LATEST_VERSION); model.setStoredVersion(MigrationModel.LATEST_VERSION);
} }

View file

@ -0,0 +1,25 @@
package org.keycloak.migration.migrators;
import java.util.List;
import org.keycloak.migration.ModelVersion;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.utils.KeycloakModelUtils;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class MigrateTo1_6_0 {
public static final ModelVersion VERSION = new ModelVersion("1.6.0");
public void migrate(KeycloakSession session) {
List<RealmModel> realms = session.realms().getRealms();
for (RealmModel realm : realms) {
KeycloakModelUtils.setupOfflineTokens(realm);
}
}
}

View file

@ -5,6 +5,7 @@ import org.keycloak.models.ClientModel;
import org.keycloak.models.Constants; import org.keycloak.models.Constants;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.models.utils.KeycloakModelUtils;
import java.util.List; import java.util.List;
@ -26,7 +27,9 @@ public class MigrationTo1_2_0_CR1 {
client.setFullScopeAllowed(false); client.setFullScopeAllowed(false);
for (String role : Constants.BROKER_SERVICE_ROLES) { for (String role : Constants.BROKER_SERVICE_ROLES) {
client.addRole(role).setDescription("${role_"+ role.toLowerCase().replaceAll("_", "-") +"}"); RoleModel roleModel = client.addRole(role);
roleModel.setDescription("${role_" + role.toLowerCase().replaceAll("_", "-") + "}");
roleModel.setScopeParamRequired(false);
} }
} }
} }

View file

@ -8,7 +8,6 @@ import java.io.Serializable;
* @version $Revision: 1 $ * @version $Revision: 1 $
*/ */
public class AuthenticationExecutionModel implements Serializable { public class AuthenticationExecutionModel implements Serializable {
private static final long serialVersionUID = 1L;
public static class ExecutionComparator implements Comparator<AuthenticationExecutionModel> { public static class ExecutionComparator implements Comparator<AuthenticationExecutionModel> {
public static final ExecutionComparator SINGLETON = new ExecutionComparator(); public static final ExecutionComparator SINGLETON = new ExecutionComparator();

View file

@ -7,7 +7,6 @@ import java.io.Serializable;
* @version $Revision: 1 $ * @version $Revision: 1 $
*/ */
public class AuthenticationFlowModel implements Serializable { public class AuthenticationFlowModel implements Serializable {
private static final long serialVersionUID = 1L;
private String id; private String id;
private String alias; private String alias;

View file

@ -9,7 +9,6 @@ import java.util.Map;
* @version $Revision: 1 $ * @version $Revision: 1 $
*/ */
public class AuthenticatorConfigModel implements Serializable { public class AuthenticatorConfigModel implements Serializable {
private static final long serialVersionUID = 1L;
private String id; private String id;
private String alias; private String alias;

View file

@ -1,5 +1,7 @@
package org.keycloak.models; package org.keycloak.models;
import org.keycloak.OAuth2Constants;
/** /**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $ * @version $Revision: 1 $
@ -16,4 +18,5 @@ public interface Constants {
String INSTALLED_APP_URL = "http://localhost"; String INSTALLED_APP_URL = "http://localhost";
String READ_TOKEN_ROLE = "read-token"; String READ_TOKEN_ROLE = "read-token";
String[] BROKER_SERVICE_ROLES = {READ_TOKEN_ROLE}; String[] BROKER_SERVICE_ROLES = {READ_TOKEN_ROLE};
String OFFLINE_ACCESS_ROLE = OAuth2Constants.OFFLINE_ACCESS;
} }

View file

@ -10,7 +10,6 @@ import java.util.Map;
* @version $Revision: 1 $ * @version $Revision: 1 $
*/ */
public class IdentityProviderMapperModel implements Serializable { public class IdentityProviderMapperModel implements Serializable {
private static final long serialVersionUID = 1L;
protected String id; protected String id;
protected String name; protected String name;

View file

@ -30,7 +30,6 @@ import org.keycloak.representations.idm.IdentityProviderRepresentation;
* @author Pedro Igor * @author Pedro Igor
*/ */
public class IdentityProviderModel implements Serializable { public class IdentityProviderModel implements Serializable {
private static final long serialVersionUID = 1L;
private String internalId; private String internalId;

View file

@ -26,6 +26,7 @@ public class ImpersonationConstants {
if (realmAdminApp.getRole(IMPERSONATION_ROLE) != null) return; if (realmAdminApp.getRole(IMPERSONATION_ROLE) != null) return;
RoleModel impersonationRole = realmAdminApp.addRole(IMPERSONATION_ROLE); RoleModel impersonationRole = realmAdminApp.addRole(IMPERSONATION_ROLE);
impersonationRole.setDescription("${role_" + IMPERSONATION_ROLE + "}"); impersonationRole.setDescription("${role_" + IMPERSONATION_ROLE + "}");
impersonationRole.setScopeParamRequired(false);
adminRole.addCompositeRole(impersonationRole); adminRole.addCompositeRole(impersonationRole);
} }
@ -36,6 +37,7 @@ public class ImpersonationConstants {
if (realmAdminApp.getRole(IMPERSONATION_ROLE) != null) return; if (realmAdminApp.getRole(IMPERSONATION_ROLE) != null) return;
RoleModel impersonationRole = realmAdminApp.addRole(IMPERSONATION_ROLE); RoleModel impersonationRole = realmAdminApp.addRole(IMPERSONATION_ROLE);
impersonationRole.setDescription("${role_" + IMPERSONATION_ROLE + "}"); impersonationRole.setDescription("${role_" + IMPERSONATION_ROLE + "}");
impersonationRole.setScopeParamRequired(false);
RoleModel adminRole = realmAdminApp.getRole(AdminRoles.REALM_ADMIN); RoleModel adminRole = realmAdminApp.getRole(AdminRoles.REALM_ADMIN);
adminRole.addCompositeRole(impersonationRole); adminRole.addCompositeRole(impersonationRole);
} }

View file

@ -0,0 +1,44 @@
package org.keycloak.models;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class OfflineClientSessionModel {
private String clientSessionId;
private String userSessionId;
private String clientId;
private String data;
public String getClientSessionId() {
return clientSessionId;
}
public void setClientSessionId(String clientSessionId) {
this.clientSessionId = clientSessionId;
}
public String getUserSessionId() {
return userSessionId;
}
public void setUserSessionId(String userSessionId) {
this.userSessionId = userSessionId;
}
public String getClientId() {
return clientId;
}
public void setClientId(String clientId) {
this.clientId = clientId;
}
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
}
}

View file

@ -0,0 +1,26 @@
package org.keycloak.models;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class OfflineUserSessionModel {
private String userSessionId;
private String data;
public String getUserSessionId() {
return userSessionId;
}
public void setUserSessionId(String userSessionId) {
this.userSessionId = userSessionId;
}
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
}
}

View file

@ -15,7 +15,6 @@ import java.util.regex.Pattern;
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a> * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/ */
public class PasswordPolicy implements Serializable { public class PasswordPolicy implements Serializable {
private static final long serialVersionUID = 1L;
public static final String INVALID_PASSWORD_MIN_LENGTH_MESSAGE = "invalidPasswordMinLengthMessage"; public static final String INVALID_PASSWORD_MIN_LENGTH_MESSAGE = "invalidPasswordMinLengthMessage";
public static final String INVALID_PASSWORD_MIN_DIGITS_MESSAGE = "invalidPasswordMinDigitsMessage"; public static final String INVALID_PASSWORD_MIN_DIGITS_MESSAGE = "invalidPasswordMinDigitsMessage";

View file

@ -10,7 +10,6 @@ import java.util.Map;
* @version $Revision: 1 $ * @version $Revision: 1 $
*/ */
public class ProtocolMapperModel implements Serializable { public class ProtocolMapperModel implements Serializable {
private static final long serialVersionUID = 1L;
protected String id; protected String id;
protected String name; protected String name;

View file

@ -10,7 +10,6 @@ import java.util.Map;
* @version $Revision: 1 $ * @version $Revision: 1 $
*/ */
public class RequiredCredentialModel implements Serializable { public class RequiredCredentialModel implements Serializable {
private static final long serialVersionUID = 1L;
protected String type; protected String type;
protected boolean input; protected boolean input;

View file

@ -17,6 +17,10 @@ public interface RoleModel {
void setName(String name); void setName(String name);
boolean isScopeParamRequired();
void setScopeParamRequired(boolean scopeParamRequired);
boolean isComposite(); boolean isComposite();
void addCompositeRole(RoleModel role); void addCompositeRole(RoleModel role);

View file

@ -7,7 +7,6 @@ import java.util.Map;
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a> * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/ */
public class UserFederationMapperModel implements Serializable { public class UserFederationMapperModel implements Serializable {
private static final long serialVersionUID = 1L;
protected String id; protected String id;
protected String name; protected String name;

View file

@ -11,7 +11,6 @@ import java.util.Map;
* @author <a href="mailto:bburke@redhat.com">Bill Burke</a> * @author <a href="mailto:bburke@redhat.com">Bill Burke</a>
*/ */
public class UserFederationProviderModel implements Serializable { public class UserFederationProviderModel implements Serializable {
private static final long serialVersionUID = 1L;
private String id; private String id;
private String providerName; private String providerName;

View file

@ -1,5 +1,6 @@
package org.keycloak.models; package org.keycloak.models;
import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
@ -113,6 +114,15 @@ public interface UserModel {
void updateConsent(UserConsentModel consent); void updateConsent(UserConsentModel consent);
boolean revokeConsentForClient(String clientInternalId); boolean revokeConsentForClient(String clientInternalId);
void addOfflineUserSession(OfflineUserSessionModel offlineUserSession);
OfflineUserSessionModel getOfflineUserSession(String userSessionId);
Collection<OfflineUserSessionModel> getOfflineUserSessions();
boolean removeOfflineUserSession(String userSessionId);
void addOfflineClientSession(OfflineClientSessionModel offlineClientSession);
OfflineClientSessionModel getOfflineClientSession(String clientSessionId);
Collection<OfflineClientSessionModel> getOfflineClientSessions();
boolean removeOfflineClientSession(String clientSessionId);
public static enum RequiredAction { public static enum RequiredAction {
VERIFY_EMAIL, UPDATE_PROFILE, CONFIGURE_TOTP, UPDATE_PASSWORD VERIFY_EMAIL, UPDATE_PROFILE, CONFIGURE_TOTP, UPDATE_PASSWORD
} }

View file

@ -40,6 +40,7 @@ public interface UserSessionModel {
public String getNote(String name); public String getNote(String name);
public void setNote(String name, String value); public void setNote(String name, String value);
public void removeNote(String name); public void removeNote(String name);
public Map<String, String> getNotes();
State getState(); State getState();
void setState(State state); void setState(State state);

View file

@ -0,0 +1,35 @@
package org.keycloak.models.entities;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class OfflineClientSessionEntity {
private String clientSessionId;
private String clientId;
private String data;
public String getClientSessionId() {
return clientSessionId;
}
public void setClientSessionId(String clientSessionId) {
this.clientSessionId = clientSessionId;
}
public String getClientId() {
return clientId;
}
public void setClientId(String clientId) {
this.clientId = clientId;
}
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
}
}

View file

@ -0,0 +1,37 @@
package org.keycloak.models.entities;
import java.util.List;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class OfflineUserSessionEntity {
private String userSessionId;
private String data;
private List<OfflineClientSessionEntity> offlineClientSessions;
public String getUserSessionId() {
return userSessionId;
}
public void setUserSessionId(String userSessionId) {
this.userSessionId = userSessionId;
}
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
}
public List<OfflineClientSessionEntity> getOfflineClientSessions() {
return offlineClientSessions;
}
public void setOfflineClientSessions(List<OfflineClientSessionEntity> offlineClientSessions) {
this.offlineClientSessions = offlineClientSessions;
}
}

View file

@ -9,6 +9,7 @@ public class RoleEntity extends AbstractIdentifiableEntity {
private String name; private String name;
private String description; private String description;
private boolean scopeParamRequired;
private List<String> compositeRoleIds; private List<String> compositeRoleIds;
@ -31,6 +32,14 @@ public class RoleEntity extends AbstractIdentifiableEntity {
this.description = description; this.description = description;
} }
public boolean isScopeParamRequired() {
return scopeParamRequired;
}
public void setScopeParamRequired(boolean scopeParamRequired) {
this.scopeParamRequired = scopeParamRequired;
}
public List<String> getCompositeRoleIds() { public List<String> getCompositeRoleIds() {
return compositeRoleIds; return compositeRoleIds;
} }

View file

@ -28,6 +28,7 @@ public class UserEntity extends AbstractIdentifiableEntity {
private List<FederatedIdentityEntity> federatedIdentities; private List<FederatedIdentityEntity> federatedIdentities;
private String federationLink; private String federationLink;
private String serviceAccountClientLink; private String serviceAccountClientLink;
private List<OfflineUserSessionEntity> offlineUserSessions;
public String getUsername() { public String getUsername() {
return username; return username;
@ -157,5 +158,13 @@ public class UserEntity extends AbstractIdentifiableEntity {
public void setServiceAccountClientLink(String serviceAccountClientLink) { public void setServiceAccountClientLink(String serviceAccountClientLink) {
this.serviceAccountClientLink = serviceAccountClientLink; this.serviceAccountClientLink = serviceAccountClientLink;
} }
public List<OfflineUserSessionEntity> getOfflineUserSessions() {
return offlineUserSessions;
}
public void setOfflineUserSessions(List<OfflineUserSessionEntity> offlineUserSessions) {
this.offlineUserSessions = offlineUserSessions;
}
} }

View file

@ -4,6 +4,7 @@ import org.bouncycastle.openssl.PEMWriter;
import org.keycloak.constants.KerberosConstants; import org.keycloak.constants.KerberosConstants;
import org.keycloak.constants.ServiceAccountConstants; import org.keycloak.constants.ServiceAccountConstants;
import org.keycloak.models.ClientModel; import org.keycloak.models.ClientModel;
import org.keycloak.models.Constants;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.KeycloakSessionTask; import org.keycloak.models.KeycloakSessionTask;
@ -360,4 +361,13 @@ public final class KeycloakModelUtils {
public static String toLowerCaseSafe(String str) { public static String toLowerCaseSafe(String str) {
return str==null ? null : str.toLowerCase(); return str==null ? null : str.toLowerCase();
} }
public static void setupOfflineTokens(RealmModel realm) {
if (realm.getRole(Constants.OFFLINE_ACCESS_ROLE) == null) {
RoleModel role = realm.addRole(Constants.OFFLINE_ACCESS_ROLE);
role.setDescription("${role_offline-access}");
role.setScopeParamRequired(true);
realm.addDefaultRole(Constants.OFFLINE_ACCESS_ROLE);
}
}
} }

View file

@ -89,6 +89,7 @@ public class ModelToRepresentation {
rep.setId(role.getId()); rep.setId(role.getId());
rep.setName(role.getName()); rep.setName(role.getName());
rep.setDescription(role.getDescription()); rep.setDescription(role.getDescription());
rep.setScopeParamRequired(role.isScopeParamRequired());
rep.setComposite(role.isComposite()); rep.setComposite(role.isComposite());
return rep; return rep;
} }

View file

@ -181,6 +181,8 @@ public class RepresentationToModel {
// Application role may already exists (for example if it is defaultRole) // Application role may already exists (for example if it is defaultRole)
RoleModel role = roleRep.getId()!=null ? client.addRole(roleRep.getId(), roleRep.getName()) : client.addRole(roleRep.getName()); RoleModel role = roleRep.getId()!=null ? client.addRole(roleRep.getId(), roleRep.getName()) : client.addRole(roleRep.getName());
role.setDescription(roleRep.getDescription()); role.setDescription(roleRep.getDescription());
boolean scopeParamRequired = roleRep.isScopeParamRequired()==null ? false : roleRep.isScopeParamRequired();
role.setScopeParamRequired(scopeParamRequired);
} }
} }
} }
@ -633,6 +635,8 @@ public class RepresentationToModel {
public static void createRole(RealmModel newRealm, RoleRepresentation roleRep) { public static void createRole(RealmModel newRealm, RoleRepresentation roleRep) {
RoleModel role = roleRep.getId()!=null ? newRealm.addRole(roleRep.getId(), roleRep.getName()) : newRealm.addRole(roleRep.getName()); RoleModel role = roleRep.getId()!=null ? newRealm.addRole(roleRep.getId(), roleRep.getName()) : newRealm.addRole(roleRep.getName());
if (roleRep.getDescription() != null) role.setDescription(roleRep.getDescription()); if (roleRep.getDescription() != null) role.setDescription(roleRep.getDescription());
boolean scopeParamRequired = roleRep.isScopeParamRequired() == null ? false : roleRep.isScopeParamRequired();
role.setScopeParamRequired(scopeParamRequired);
} }
private static void addComposites(RoleModel role, RoleRepresentation roleRep, RealmModel realm) { private static void addComposites(RoleModel role, RoleRepresentation roleRep, RealmModel realm) {

View file

@ -1,12 +1,15 @@
package org.keycloak.models.utils; package org.keycloak.models.utils;
import org.keycloak.models.ClientModel; import org.keycloak.models.ClientModel;
import org.keycloak.models.OfflineClientSessionModel;
import org.keycloak.models.OfflineUserSessionModel;
import org.keycloak.models.UserConsentModel; import org.keycloak.models.UserConsentModel;
import org.keycloak.models.RoleModel; import org.keycloak.models.RoleModel;
import org.keycloak.models.UserCredentialModel; import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserCredentialValueModel; import org.keycloak.models.UserCredentialValueModel;
import org.keycloak.models.UserModel; import org.keycloak.models.UserModel;
import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
@ -255,4 +258,44 @@ public class UserModelDelegate implements UserModel {
public void setCreatedTimestamp(Long timestamp){ public void setCreatedTimestamp(Long timestamp){
delegate.setCreatedTimestamp(timestamp); delegate.setCreatedTimestamp(timestamp);
} }
@Override
public void addOfflineUserSession(OfflineUserSessionModel userSession) {
delegate.addOfflineUserSession(userSession);
}
@Override
public OfflineUserSessionModel getOfflineUserSession(String userSessionId) {
return delegate.getOfflineUserSession(userSessionId);
}
@Override
public Collection<OfflineUserSessionModel> getOfflineUserSessions() {
return delegate.getOfflineUserSessions();
}
@Override
public boolean removeOfflineUserSession(String userSessionId) {
return delegate.removeOfflineUserSession(userSessionId);
}
@Override
public void addOfflineClientSession(OfflineClientSessionModel clientSession) {
delegate.addOfflineClientSession(clientSession);
}
@Override
public OfflineClientSessionModel getOfflineClientSession(String clientSessionId) {
return delegate.getOfflineClientSession(clientSessionId);
}
@Override
public Collection<OfflineClientSessionModel> getOfflineClientSessions() {
return delegate.getOfflineClientSessions();
}
@Override
public boolean removeOfflineClientSession(String clientSessionId) {
return delegate.removeOfflineClientSession(clientSessionId);
}
} }

View file

@ -88,6 +88,16 @@ public class RoleAdapter implements RoleModel {
role.setDescription(description); role.setDescription(description);
} }
@Override
public boolean isScopeParamRequired() {
return role.isScopeParamRequired();
}
@Override
public void setScopeParamRequired(boolean scopeParamRequired) {
role.setScopeParamRequired(scopeParamRequired);
}
@Override @Override
public boolean isComposite() { public boolean isComposite() {
return role.getCompositeRoleIds() != null && role.getCompositeRoleIds().size() > 0; return role.getCompositeRoleIds() != null && role.getCompositeRoleIds().size() > 0;

View file

@ -22,7 +22,10 @@ import org.keycloak.models.ClientModel;
import static org.keycloak.models.utils.Pbkdf2PasswordEncoder.getSalt; import static org.keycloak.models.utils.Pbkdf2PasswordEncoder.getSalt;
import org.keycloak.models.ModelDuplicateException; import org.keycloak.models.ModelDuplicateException;
import org.keycloak.models.ModelException;
import org.keycloak.models.OTPPolicy; import org.keycloak.models.OTPPolicy;
import org.keycloak.models.OfflineClientSessionModel;
import org.keycloak.models.OfflineUserSessionModel;
import org.keycloak.models.UserConsentModel; import org.keycloak.models.UserConsentModel;
import org.keycloak.models.PasswordPolicy; import org.keycloak.models.PasswordPolicy;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
@ -32,6 +35,8 @@ import org.keycloak.models.UserCredentialValueModel;
import org.keycloak.models.UserModel; import org.keycloak.models.UserModel;
import org.keycloak.models.entities.CredentialEntity; import org.keycloak.models.entities.CredentialEntity;
import org.keycloak.models.entities.FederatedIdentityEntity; import org.keycloak.models.entities.FederatedIdentityEntity;
import org.keycloak.models.entities.OfflineClientSessionEntity;
import org.keycloak.models.entities.OfflineUserSessionEntity;
import org.keycloak.models.entities.RoleEntity; import org.keycloak.models.entities.RoleEntity;
import org.keycloak.models.entities.UserEntity; import org.keycloak.models.entities.UserEntity;
import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.models.utils.KeycloakModelUtils;
@ -39,6 +44,7 @@ import org.keycloak.models.utils.Pbkdf2PasswordEncoder;
import org.keycloak.util.Time; import org.keycloak.util.Time;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.HashMap; import java.util.HashMap;
@ -216,7 +222,7 @@ public class UserAdapter implements UserModel, Comparable {
@Override @Override
public Map<String, List<String>> getAttributes() { public Map<String, List<String>> getAttributes() {
return user.getAttributes()==null ? Collections.<String, List<String>>emptyMap() : Collections.unmodifiableMap((Map)user.getAttributes()); return user.getAttributes()==null ? Collections.<String, List<String>>emptyMap() : Collections.unmodifiableMap((Map) user.getAttributes());
} }
@Override @Override
@ -568,6 +574,142 @@ public class UserAdapter implements UserModel, Comparable {
return false; return false;
} }
@Override
public void addOfflineUserSession(OfflineUserSessionModel userSession) {
if (user.getOfflineUserSessions() == null) {
user.setOfflineUserSessions(new ArrayList<OfflineUserSessionEntity>());
}
if (getUserSessionEntityById(userSession.getUserSessionId()) != null) {
throw new ModelDuplicateException("User session already exists with id " + userSession.getUserSessionId() + " for user " + user.getUsername());
}
OfflineUserSessionEntity entity = new OfflineUserSessionEntity();
entity.setUserSessionId(userSession.getUserSessionId());
entity.setData(userSession.getData());
entity.setOfflineClientSessions(new ArrayList<OfflineClientSessionEntity>());
user.getOfflineUserSessions().add(entity);
}
@Override
public OfflineUserSessionModel getOfflineUserSession(String userSessionId) {
OfflineUserSessionEntity entity = getUserSessionEntityById(userSessionId);
return entity==null ? null : toModel(entity);
}
@Override
public Collection<OfflineUserSessionModel> getOfflineUserSessions() {
if (user.getOfflineUserSessions()==null) {
return Collections.emptyList();
} else {
List<OfflineUserSessionModel> result = new ArrayList<>();
for (OfflineUserSessionEntity entity : user.getOfflineUserSessions()) {
result.add(toModel(entity));
}
return result;
}
}
private OfflineUserSessionModel toModel(OfflineUserSessionEntity entity) {
OfflineUserSessionModel model = new OfflineUserSessionModel();
model.setUserSessionId(entity.getUserSessionId());
model.setData(entity.getData());
return model;
}
@Override
public boolean removeOfflineUserSession(String userSessionId) {
OfflineUserSessionEntity entity = getUserSessionEntityById(userSessionId);
if (entity != null) {
user.getOfflineUserSessions().remove(entity);
return true;
} else {
return false;
}
}
private OfflineUserSessionEntity getUserSessionEntityById(String userSessionId) {
if (user.getOfflineUserSessions() != null) {
for (OfflineUserSessionEntity entity : user.getOfflineUserSessions()) {
if (entity.getUserSessionId().equals(userSessionId)) {
return entity;
}
}
}
return null;
}
@Override
public void addOfflineClientSession(OfflineClientSessionModel clientSession) {
OfflineUserSessionEntity userSessionEntity = getUserSessionEntityById(clientSession.getUserSessionId());
if (userSessionEntity == null) {
throw new ModelException("OfflineUserSession with ID " + clientSession.getUserSessionId() + " doesn't exist for user " + user.getUsername());
}
OfflineClientSessionEntity clEntity = new OfflineClientSessionEntity();
clEntity.setClientSessionId(clientSession.getClientSessionId());
clEntity.setClientId(clientSession.getClientId());
clEntity.setData(clientSession.getData());
userSessionEntity.getOfflineClientSessions().add(clEntity);
}
@Override
public OfflineClientSessionModel getOfflineClientSession(String clientSessionId) {
if (user.getOfflineUserSessions() != null) {
for (OfflineUserSessionEntity userSession : user.getOfflineUserSessions()) {
for (OfflineClientSessionEntity clSession : userSession.getOfflineClientSessions()) {
if (clSession.getClientSessionId().equals(clientSessionId)) {
return toModel(clSession, userSession.getUserSessionId());
}
}
}
}
return null;
}
private OfflineClientSessionModel toModel(OfflineClientSessionEntity cls, String userSessionId) {
OfflineClientSessionModel model = new OfflineClientSessionModel();
model.setClientSessionId(cls.getClientSessionId());
model.setClientId(cls.getClientId());
model.setData(cls.getData());
model.setUserSessionId(userSessionId);
return model;
}
@Override
public Collection<OfflineClientSessionModel> getOfflineClientSessions() {
List<OfflineClientSessionModel> result = new ArrayList<>();
if (user.getOfflineUserSessions() != null) {
for (OfflineUserSessionEntity userSession : user.getOfflineUserSessions()) {
for (OfflineClientSessionEntity clSession : userSession.getOfflineClientSessions()) {
result.add(toModel(clSession, userSession.getUserSessionId()));
}
}
}
return result;
}
@Override
public boolean removeOfflineClientSession(String clientSessionId) {
if (user.getOfflineUserSessions() != null) {
for (OfflineUserSessionEntity userSession : user.getOfflineUserSessions()) {
for (OfflineClientSessionEntity clSession : userSession.getOfflineClientSessions()) {
if (clSession.getClientSessionId().equals(clientSessionId)) {
userSession.getOfflineClientSessions().remove(clSession);
return true;
}
}
}
}
return false;
}
@Override @Override
public boolean equals(Object o) { public boolean equals(Object o) {
if (this == o) return true; if (this == o) return true;

View file

@ -319,6 +319,7 @@ public class DefaultCacheUserProvider implements CacheUserProvider {
@Override @Override
public void preRemove(RealmModel realm, ClientModel client) { public void preRemove(RealmModel realm, ClientModel client) {
realmInvalidations.add(realm.getId()); // easier to just invalidate whole realm
getDelegate().preRemove(realm, client); getDelegate().preRemove(realm, client);
} }

View file

@ -59,6 +59,18 @@ public class RoleAdapter implements RoleModel {
updated.setDescription(description); updated.setDescription(description);
} }
@Override
public boolean isScopeParamRequired() {
if (updated != null) return updated.isScopeParamRequired();
return cached.isScopeParamRequired();
}
@Override
public void setScopeParamRequired(boolean scopeParamRequired) {
getDelegateForUpdate();
updated.setScopeParamRequired(scopeParamRequired);
}
@Override @Override
public String getId() { public String getId() {
if (updated != null) return updated.getId(); if (updated != null) return updated.getId();

View file

@ -348,4 +348,52 @@ public class UserAdapter implements UserModel {
getDelegateForUpdate(); getDelegateForUpdate();
return updated.revokeConsentForClient(clientId); return updated.revokeConsentForClient(clientId);
} }
@Override
public void addOfflineUserSession(OfflineUserSessionModel userSession) {
getDelegateForUpdate();
updated.addOfflineUserSession(userSession);
}
@Override
public OfflineUserSessionModel getOfflineUserSession(String userSessionId) {
if (updated != null) return updated.getOfflineUserSession(userSessionId);
return cached.getOfflineUserSessions().get(userSessionId);
}
@Override
public Collection<OfflineUserSessionModel> getOfflineUserSessions() {
if (updated != null) return updated.getOfflineUserSessions();
return cached.getOfflineUserSessions().values();
}
@Override
public boolean removeOfflineUserSession(String userSessionId) {
getDelegateForUpdate();
return updated.removeOfflineUserSession(userSessionId);
}
@Override
public void addOfflineClientSession(OfflineClientSessionModel clientSession) {
getDelegateForUpdate();
updated.addOfflineClientSession(clientSession);
}
@Override
public OfflineClientSessionModel getOfflineClientSession(String clientSessionId) {
if (updated != null) return updated.getOfflineClientSession(clientSessionId);
return cached.getOfflineClientSessions().get(clientSessionId);
}
@Override
public Collection<OfflineClientSessionModel> getOfflineClientSessions() {
if (updated != null) return updated.getOfflineClientSessions();
return cached.getOfflineClientSessions().values();
}
@Override
public boolean removeOfflineClientSession(String clientSessionId) {
getDelegateForUpdate();
return updated.removeOfflineClientSession(clientSessionId);
}
} }

View file

@ -21,7 +21,6 @@ import java.util.TreeMap;
* @version $Revision: 1 $ * @version $Revision: 1 $
*/ */
public class CachedClient implements Serializable { public class CachedClient implements Serializable {
private static final long serialVersionUID = 1L;
private String id; private String id;
private String clientId; private String clientId;

View file

@ -9,8 +9,6 @@ import org.keycloak.models.RoleModel;
*/ */
public class CachedClientRole extends CachedRole { public class CachedClientRole extends CachedRole {
private static final long serialVersionUID = 1L;
private final String idClient; private final String idClient;
public CachedClientRole(String idClient, RoleModel model, RealmModel realm) { public CachedClientRole(String idClient, RoleModel model, RealmModel realm) {

View file

@ -33,7 +33,6 @@ import java.util.Set;
* @version $Revision: 1 $ * @version $Revision: 1 $
*/ */
public class CachedRealm implements Serializable { public class CachedRealm implements Serializable {
private static final long serialVersionUID = 1L;
private String id; private String id;
private String name; private String name;

View file

@ -13,12 +13,11 @@ import java.util.Set;
*/ */
public class CachedRole implements Serializable { public class CachedRole implements Serializable {
private static final long serialVersionUID = 1L;
final protected String id; final protected String id;
final protected String name; final protected String name;
final protected String realm; final protected String realm;
final protected String description; final protected String description;
final protected Boolean scopeParamRequired;
final protected boolean composite; final protected boolean composite;
final protected Set<String> composites = new HashSet<String>(); final protected Set<String> composites = new HashSet<String>();
@ -27,6 +26,7 @@ public class CachedRole implements Serializable {
description = model.getDescription(); description = model.getDescription();
id = model.getId(); id = model.getId();
name = model.getName(); name = model.getName();
scopeParamRequired = model.isScopeParamRequired();
this.realm = realm.getId(); this.realm = realm.getId();
if (composite) { if (composite) {
for (RoleModel child : model.getComposites()) { for (RoleModel child : model.getComposites()) {
@ -52,6 +52,10 @@ public class CachedRole implements Serializable {
return description; return description;
} }
public Boolean isScopeParamRequired() {
return scopeParamRequired;
}
public boolean isComposite() { public boolean isComposite() {
return composite; return composite;
} }

View file

@ -1,5 +1,7 @@
package org.keycloak.models.cache.entities; package org.keycloak.models.cache.entities;
import org.keycloak.models.OfflineClientSessionModel;
import org.keycloak.models.OfflineUserSessionModel;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel; import org.keycloak.models.RoleModel;
import org.keycloak.models.UserCredentialValueModel; import org.keycloak.models.UserCredentialValueModel;
@ -7,9 +9,11 @@ import org.keycloak.models.UserModel;
import org.keycloak.util.MultivaluedHashMap; import org.keycloak.util.MultivaluedHashMap;
import java.io.Serializable; import java.io.Serializable;
import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set; import java.util.Set;
/** /**
@ -33,6 +37,8 @@ public class CachedUser implements Serializable {
private MultivaluedHashMap<String, String> attributes = new MultivaluedHashMap<>(); private MultivaluedHashMap<String, String> attributes = new MultivaluedHashMap<>();
private Set<String> requiredActions = new HashSet<>(); private Set<String> requiredActions = new HashSet<>();
private Set<String> roleMappings = new HashSet<String>(); private Set<String> roleMappings = new HashSet<String>();
private Map<String, OfflineUserSessionModel> offlineUserSessions = new HashMap<>();
private Map<String, OfflineClientSessionModel> offlineClientSessions = new HashMap<>();
public CachedUser(RealmModel realm, UserModel user) { public CachedUser(RealmModel realm, UserModel user) {
this.id = user.getId(); this.id = user.getId();
@ -53,6 +59,12 @@ public class CachedUser implements Serializable {
for (RoleModel role : user.getRoleMappings()) { for (RoleModel role : user.getRoleMappings()) {
roleMappings.add(role.getId()); roleMappings.add(role.getId());
} }
for (OfflineUserSessionModel offlineSession : user.getOfflineUserSessions()) {
offlineUserSessions.put(offlineSession.getUserSessionId(), offlineSession);
}
for (OfflineClientSessionModel offlineSession : user.getOfflineClientSessions()) {
offlineClientSessions.put(offlineSession.getClientSessionId(), offlineSession);
}
} }
public String getId() { public String getId() {
@ -118,4 +130,12 @@ public class CachedUser implements Serializable {
public String getServiceAccountClientLink() { public String getServiceAccountClientLink() {
return serviceAccountClientLink; return serviceAccountClientLink;
} }
public Map<String, OfflineUserSessionModel> getOfflineUserSessions() {
return offlineUserSessions;
}
public Map<String, OfflineClientSessionModel> getOfflineClientSessions() {
return offlineClientSessions;
}
} }

View file

@ -17,17 +17,14 @@
<dependency> <dependency>
<groupId>org.bouncycastle</groupId> <groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId> <artifactId>bcprov-jdk15on</artifactId>
<scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>net.iharder</groupId> <groupId>net.iharder</groupId>
<artifactId>base64</artifactId> <artifactId>base64</artifactId>
<scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<artifactId>keycloak-core</artifactId> <artifactId>keycloak-core</artifactId>
<scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
@ -44,18 +41,15 @@
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.hibernate.javax.persistence</groupId> <groupId>org.hibernate.javax.persistence</groupId>
<artifactId>hibernate-jpa-2.1-api</artifactId> <artifactId>${hibernate.javax.persistence.artifactId}</artifactId>
<scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.hibernate</groupId> <groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId> <artifactId>hibernate-entitymanager</artifactId>
<scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.jboss.resteasy</groupId> <groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-jaxrs</artifactId> <artifactId>resteasy-jaxrs</artifactId>
<scope>provided</scope>
<exclusions> <exclusions>
<exclusion> <exclusion>
<groupId>log4j</groupId> <groupId>log4j</groupId>

View file

@ -169,6 +169,10 @@ public class JpaUserProvider implements UserProvider {
.setParameter("realmId", realm.getId()).executeUpdate(); .setParameter("realmId", realm.getId()).executeUpdate();
num = em.createNamedQuery("deleteUserAttributesByRealm") num = em.createNamedQuery("deleteUserAttributesByRealm")
.setParameter("realmId", realm.getId()).executeUpdate(); .setParameter("realmId", realm.getId()).executeUpdate();
num = em.createNamedQuery("deleteOfflineClientSessionsByRealm")
.setParameter("realmId", realm.getId()).executeUpdate();
num = em.createNamedQuery("deleteOfflineUserSessionsByRealm")
.setParameter("realmId", realm.getId()).executeUpdate();
num = em.createNamedQuery("deleteUsersByRealm") num = em.createNamedQuery("deleteUsersByRealm")
.setParameter("realmId", realm.getId()).executeUpdate(); .setParameter("realmId", realm.getId()).executeUpdate();
} }
@ -195,6 +199,14 @@ public class JpaUserProvider implements UserProvider {
.setParameter("realmId", realm.getId()) .setParameter("realmId", realm.getId())
.setParameter("link", link.getId()) .setParameter("link", link.getId())
.executeUpdate(); .executeUpdate();
num = em.createNamedQuery("deleteOfflineClientSessionsByRealmAndLink")
.setParameter("realmId", realm.getId())
.setParameter("link", link.getId())
.executeUpdate();
num = em.createNamedQuery("deleteOfflineUserSessionsByRealmAndLink")
.setParameter("realmId", realm.getId())
.setParameter("link", link.getId())
.executeUpdate();
num = em.createNamedQuery("deleteUsersByRealmAndLink") num = em.createNamedQuery("deleteUsersByRealmAndLink")
.setParameter("realmId", realm.getId()) .setParameter("realmId", realm.getId())
.setParameter("link", link.getId()) .setParameter("link", link.getId())
@ -212,6 +224,8 @@ public class JpaUserProvider implements UserProvider {
em.createNamedQuery("deleteUserConsentProtMappersByClient").setParameter("clientId", client.getId()).executeUpdate(); em.createNamedQuery("deleteUserConsentProtMappersByClient").setParameter("clientId", client.getId()).executeUpdate();
em.createNamedQuery("deleteUserConsentRolesByClient").setParameter("clientId", client.getId()).executeUpdate(); em.createNamedQuery("deleteUserConsentRolesByClient").setParameter("clientId", client.getId()).executeUpdate();
em.createNamedQuery("deleteUserConsentsByClient").setParameter("clientId", client.getId()).executeUpdate(); em.createNamedQuery("deleteUserConsentsByClient").setParameter("clientId", client.getId()).executeUpdate();
em.createNamedQuery("deleteOfflineClientSessionsByClient").setParameter("clientId", client.getId()).executeUpdate();
em.createNamedQuery("deleteDetachedOfflineUserSessions").executeUpdate();
} }
@Override @Override

View file

@ -49,6 +49,16 @@ public class RoleAdapter implements RoleModel {
role.setDescription(description); role.setDescription(description);
} }
@Override
public boolean isScopeParamRequired() {
return role.isScopeParamRequired();
}
@Override
public void setScopeParamRequired(boolean scopeParamRequired) {
role.setScopeParamRequired(scopeParamRequired);
}
@Override @Override
public String getId() { public String getId() {
return role.getId(); return role.getId();

View file

@ -2,6 +2,8 @@ package org.keycloak.models.jpa;
import org.keycloak.models.ClientModel; import org.keycloak.models.ClientModel;
import org.keycloak.models.OTPPolicy; import org.keycloak.models.OTPPolicy;
import org.keycloak.models.OfflineClientSessionModel;
import org.keycloak.models.OfflineUserSessionModel;
import org.keycloak.models.ProtocolMapperModel; import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.UserConsentModel; import org.keycloak.models.UserConsentModel;
import org.keycloak.models.ModelDuplicateException; import org.keycloak.models.ModelDuplicateException;
@ -14,6 +16,8 @@ import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserCredentialValueModel; import org.keycloak.models.UserCredentialValueModel;
import org.keycloak.models.UserModel; import org.keycloak.models.UserModel;
import org.keycloak.models.jpa.entities.CredentialEntity; import org.keycloak.models.jpa.entities.CredentialEntity;
import org.keycloak.models.jpa.entities.OfflineClientSessionEntity;
import org.keycloak.models.jpa.entities.OfflineUserSessionEntity;
import org.keycloak.models.jpa.entities.UserConsentEntity; import org.keycloak.models.jpa.entities.UserConsentEntity;
import org.keycloak.models.jpa.entities.UserConsentProtocolMapperEntity; import org.keycloak.models.jpa.entities.UserConsentProtocolMapperEntity;
import org.keycloak.models.jpa.entities.UserConsentRoleEntity; import org.keycloak.models.jpa.entities.UserConsentRoleEntity;
@ -37,6 +41,7 @@ import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
@ -750,6 +755,124 @@ public class UserAdapter implements UserModel {
em.flush(); em.flush();
} }
@Override
public void addOfflineUserSession(OfflineUserSessionModel offlineSession) {
OfflineUserSessionEntity entity = new OfflineUserSessionEntity();
entity.setUser(user);
entity.setUserSessionId(offlineSession.getUserSessionId());
entity.setData(offlineSession.getData());
em.persist(entity);
user.getOfflineUserSessions().add(entity);
em.flush();
}
@Override
public OfflineUserSessionModel getOfflineUserSession(String userSessionId) {
for (OfflineUserSessionEntity entity : user.getOfflineUserSessions()) {
if (entity.getUserSessionId().equals(userSessionId)) {
return toModel(entity);
}
}
return null;
}
private OfflineUserSessionModel toModel(OfflineUserSessionEntity entity) {
OfflineUserSessionModel model = new OfflineUserSessionModel();
model.setUserSessionId(entity.getUserSessionId());
model.setData(entity.getData());
return model;
}
@Override
public Collection<OfflineUserSessionModel> getOfflineUserSessions() {
List<OfflineUserSessionModel> result = new LinkedList<>();
for (OfflineUserSessionEntity entity : user.getOfflineUserSessions()) {
result.add(toModel(entity));
}
return result;
}
@Override
public boolean removeOfflineUserSession(String userSessionId) {
OfflineUserSessionEntity found = null;
for (OfflineUserSessionEntity session : user.getOfflineUserSessions()) {
if (session.getUserSessionId().equals(userSessionId)) {
found = session;
break;
}
}
if (found == null) {
return false;
} else {
user.getOfflineUserSessions().remove(found);
em.remove(found);
em.flush();
return true;
}
}
@Override
public void addOfflineClientSession(OfflineClientSessionModel clientSession) {
OfflineClientSessionEntity entity = new OfflineClientSessionEntity();
entity.setUser(user);
entity.setClientSessionId(clientSession.getClientSessionId());
entity.setUserSessionId(clientSession.getUserSessionId());
entity.setClientId(clientSession.getClientId());
entity.setData(clientSession.getData());
em.persist(entity);
user.getOfflineClientSessions().add(entity);
em.flush();
}
@Override
public OfflineClientSessionModel getOfflineClientSession(String clientSessionId) {
for (OfflineClientSessionEntity entity : user.getOfflineClientSessions()) {
if (entity.getClientSessionId().equals(clientSessionId)) {
return toModel(entity);
}
}
return null;
}
private OfflineClientSessionModel toModel(OfflineClientSessionEntity entity) {
OfflineClientSessionModel model = new OfflineClientSessionModel();
model.setClientSessionId(entity.getClientSessionId());
model.setClientId(entity.getClientId());
model.setUserSessionId(entity.getUserSessionId());
model.setData(entity.getData());
return model;
}
@Override
public Collection<OfflineClientSessionModel> getOfflineClientSessions() {
List<OfflineClientSessionModel> result = new LinkedList<>();
for (OfflineClientSessionEntity entity : user.getOfflineClientSessions()) {
result.add(toModel(entity));
}
return result;
}
@Override
public boolean removeOfflineClientSession(String clientSessionId) {
OfflineClientSessionEntity found = null;
for (OfflineClientSessionEntity session : user.getOfflineClientSessions()) {
if (session.getClientSessionId().equals(clientSessionId)) {
found = session;
break;
}
}
if (found == null) {
return false;
} else {
user.getOfflineClientSessions().remove(found);
em.remove(found);
em.flush();
return true;
}
}
@Override @Override
public boolean equals(Object o) { public boolean equals(Object o) {
if (this == o) return true; if (this == o) return true;

View file

@ -0,0 +1,81 @@
package org.keycloak.models.jpa.entities;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.Table;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
@NamedQueries({
@NamedQuery(name="deleteOfflineClientSessionsByRealm", query="delete from OfflineClientSessionEntity sess where sess.user IN (select u from UserEntity u where u.realmId=:realmId)"),
@NamedQuery(name="deleteOfflineClientSessionsByRealmAndLink", query="delete from OfflineClientSessionEntity sess where sess.user IN (select u from UserEntity u where u.realmId=:realmId and u.federationLink=:link)"),
@NamedQuery(name="deleteOfflineClientSessionsByClient", query="delete from OfflineClientSessionEntity sess where sess.clientId=:clientId")
})
@Table(name="OFFLINE_CLIENT_SESSION")
@Entity
public class OfflineClientSessionEntity {
@Id
@Column(name="CLIENT_SESSION_ID", length = 36)
protected String clientSessionId;
@Column(name="USER_SESSION_ID", length = 36)
protected String userSessionId;
@Column(name="CLIENT_ID", length = 36)
protected String clientId;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name="USER_ID")
protected UserEntity user;
@Column(name="DATA")
protected String data;
public String getClientSessionId() {
return clientSessionId;
}
public void setClientSessionId(String clientSessionId) {
this.clientSessionId = clientSessionId;
}
public String getUserSessionId() {
return userSessionId;
}
public void setUserSessionId(String userSessionId) {
this.userSessionId = userSessionId;
}
public String getClientId() {
return clientId;
}
public void setClientId(String clientId) {
this.clientId = clientId;
}
public UserEntity getUser() {
return user;
}
public void setUser(UserEntity user) {
this.user = user;
}
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
}
}

View file

@ -0,0 +1,59 @@
package org.keycloak.models.jpa.entities;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.Table;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
@NamedQueries({
@NamedQuery(name="deleteOfflineUserSessionsByRealm", query="delete from OfflineUserSessionEntity sess where sess.user IN (select u from UserEntity u where u.realmId=:realmId)"),
@NamedQuery(name="deleteOfflineUserSessionsByRealmAndLink", query="delete from OfflineUserSessionEntity sess where sess.user IN (select u from UserEntity u where u.realmId=:realmId and u.federationLink=:link)"),
@NamedQuery(name="deleteDetachedOfflineUserSessions", query="delete from OfflineUserSessionEntity sess where sess.userSessionId NOT IN (select c.userSessionId from OfflineClientSessionEntity c)")
})
@Table(name="OFFLINE_USER_SESSION")
@Entity
public class OfflineUserSessionEntity {
@Id
@Column(name="USER_SESSION_ID", length = 36)
protected String userSessionId;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name="USER_ID")
protected UserEntity user;
@Column(name="DATA")
protected String data;
public String getUserSessionId() {
return userSessionId;
}
public void setUserSessionId(String userSessionId) {
this.userSessionId = userSessionId;
}
public UserEntity getUser() {
return user;
}
public void setUser(UserEntity user) {
this.user = user;
}
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
}
}

View file

@ -37,6 +37,8 @@ public class RoleEntity {
private String name; private String name;
@Column(name = "DESCRIPTION") @Column(name = "DESCRIPTION")
private String description; private String description;
@Column(name = "SCOPE_PARAM_REQUIRED")
private boolean scopeParamRequired;
// hax! couldn't get constraint to work properly // hax! couldn't get constraint to work properly
@Column(name = "REALM_ID") @Column(name = "REALM_ID")
@ -93,6 +95,14 @@ public class RoleEntity {
this.description = description; this.description = description;
} }
public boolean isScopeParamRequired() {
return scopeParamRequired;
}
public void setScopeParamRequired(boolean scopeParamRequired) {
this.scopeParamRequired = scopeParamRequired;
}
public Collection<RoleEntity> getCompositeRoles() { public Collection<RoleEntity> getCompositeRoles() {
return compositeRoles; return compositeRoles;
} }

View file

@ -3,9 +3,13 @@ package org.keycloak.models.jpa.entities;
import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.models.utils.KeycloakModelUtils;
import javax.persistence.CascadeType; import javax.persistence.CascadeType;
import javax.persistence.CollectionTable;
import javax.persistence.Column; import javax.persistence.Column;
import javax.persistence.ElementCollection;
import javax.persistence.Entity; import javax.persistence.Entity;
import javax.persistence.Id; import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.MapKeyColumn;
import javax.persistence.NamedQueries; import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery; import javax.persistence.NamedQuery;
import javax.persistence.OneToMany; import javax.persistence.OneToMany;
@ -14,6 +18,8 @@ import javax.persistence.UniqueConstraint;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
/** /**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@ -83,6 +89,12 @@ public class UserEntity {
@Column(name="SERVICE_ACCOUNT_CLIENT_LINK") @Column(name="SERVICE_ACCOUNT_CLIENT_LINK")
protected String serviceAccountClientLink; protected String serviceAccountClientLink;
@OneToMany(cascade = CascadeType.REMOVE, orphanRemoval = true, mappedBy="user")
protected Collection<OfflineUserSessionEntity> offlineUserSessions = new ArrayList<>();
@OneToMany(cascade = CascadeType.REMOVE, orphanRemoval = true, mappedBy="user")
protected Collection<OfflineClientSessionEntity> offlineClientSessions = new ArrayList<>();
public String getId() { public String getId() {
return id; return id;
} }
@ -212,6 +224,22 @@ public class UserEntity {
this.serviceAccountClientLink = serviceAccountClientLink; this.serviceAccountClientLink = serviceAccountClientLink;
} }
public Collection<OfflineUserSessionEntity> getOfflineUserSessions() {
return offlineUserSessions;
}
public void setOfflineUserSessions(Collection<OfflineUserSessionEntity> offlineUserSessions) {
this.offlineUserSessions = offlineUserSessions;
}
public Collection<OfflineClientSessionEntity> getOfflineClientSessions() {
return offlineClientSessions;
}
public void setOfflineClientSessions(Collection<OfflineClientSessionEntity> offlineClientSessions) {
this.offlineClientSessions = offlineClientSessions;
}
@Override @Override
public boolean equals(Object o) { public boolean equals(Object o) {
if (this == o) return true; if (this == o) return true;

View file

@ -19,6 +19,8 @@ import org.keycloak.models.UserFederationProviderModel;
import org.keycloak.models.UserModel; import org.keycloak.models.UserModel;
import org.keycloak.models.UserProvider; import org.keycloak.models.UserProvider;
import org.keycloak.models.entities.FederatedIdentityEntity; import org.keycloak.models.entities.FederatedIdentityEntity;
import org.keycloak.models.entities.OfflineClientSessionEntity;
import org.keycloak.models.entities.OfflineUserSessionEntity;
import org.keycloak.models.mongo.keycloak.entities.MongoUserConsentEntity; import org.keycloak.models.mongo.keycloak.entities.MongoUserConsentEntity;
import org.keycloak.models.mongo.keycloak.entities.MongoUserEntity; import org.keycloak.models.mongo.keycloak.entities.MongoUserEntity;
import org.keycloak.models.utils.CredentialValidation; import org.keycloak.models.utils.CredentialValidation;
@ -399,6 +401,36 @@ public class MongoUserProvider implements UserProvider {
.and("clientId").is(client.getId()) .and("clientId").is(client.getId())
.get(); .get();
getMongoStore().removeEntities(MongoUserConsentEntity.class, query, false, invocationContext); getMongoStore().removeEntities(MongoUserConsentEntity.class, query, false, invocationContext);
// Remove all offlineClientSessions
query = new QueryBuilder()
.and("offlineUserSessions.offlineClientSessions.clientId").is(client.getId())
.get();
List<MongoUserEntity> users = getMongoStore().loadEntities(MongoUserEntity.class, query, invocationContext);
for (MongoUserEntity user : users) {
boolean anyRemoved = false;
for (OfflineUserSessionEntity userSession : user.getOfflineUserSessions()) {
for (OfflineClientSessionEntity clientSession : userSession.getOfflineClientSessions()) {
if (clientSession.getClientId().equals(client.getId())) {
userSession.getOfflineClientSessions().remove(clientSession);
anyRemoved = true;
break;
}
}
// Check if it was last clientSession. Then remove userSession too
if (userSession.getOfflineClientSessions().size() == 0) {
user.getOfflineUserSessions().remove(userSession);
anyRemoved = true;
break;
}
}
if (anyRemoved) {
getMongoStore().updateEntity(user, invocationContext);
}
}
} }
@Override @Override

View file

@ -68,6 +68,17 @@ public class RoleAdapter extends AbstractMongoAdapter<MongoRoleEntity> implement
updateRole(); updateRole();
} }
@Override
public boolean isScopeParamRequired() {
return role.isScopeParamRequired();
}
@Override
public void setScopeParamRequired(boolean scopeParamRequired) {
role.setScopeParamRequired(scopeParamRequired);
updateRole();
}
@Override @Override
public boolean isComposite() { public boolean isComposite() {
return role.getCompositeRoleIds() != null && role.getCompositeRoleIds().size() > 0; return role.getCompositeRoleIds() != null && role.getCompositeRoleIds().size() > 0;

View file

@ -8,6 +8,8 @@ import com.mongodb.QueryBuilder;
import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext; import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext;
import org.keycloak.models.ClientModel; import org.keycloak.models.ClientModel;
import org.keycloak.models.OTPPolicy; import org.keycloak.models.OTPPolicy;
import org.keycloak.models.OfflineClientSessionModel;
import org.keycloak.models.OfflineUserSessionModel;
import org.keycloak.models.ProtocolMapperModel; import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.UserConsentModel; import org.keycloak.models.UserConsentModel;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
@ -20,6 +22,8 @@ import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserCredentialValueModel; import org.keycloak.models.UserCredentialValueModel;
import org.keycloak.models.UserModel; import org.keycloak.models.UserModel;
import org.keycloak.models.entities.CredentialEntity; import org.keycloak.models.entities.CredentialEntity;
import org.keycloak.models.entities.OfflineClientSessionEntity;
import org.keycloak.models.entities.OfflineUserSessionEntity;
import org.keycloak.models.entities.UserConsentEntity; import org.keycloak.models.entities.UserConsentEntity;
import org.keycloak.models.mongo.keycloak.entities.MongoUserConsentEntity; import org.keycloak.models.mongo.keycloak.entities.MongoUserConsentEntity;
import org.keycloak.models.mongo.keycloak.entities.MongoUserEntity; import org.keycloak.models.mongo.keycloak.entities.MongoUserEntity;
@ -30,6 +34,7 @@ import org.keycloak.models.utils.Pbkdf2PasswordEncoder;
import org.keycloak.util.Time; import org.keycloak.util.Time;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.HashMap; import java.util.HashMap;
@ -627,6 +632,145 @@ public class UserAdapter extends AbstractMongoAdapter<MongoUserEntity> implement
return getMongoStore().removeEntity(entity, invocationContext); return getMongoStore().removeEntity(entity, invocationContext);
} }
@Override
public void addOfflineUserSession(OfflineUserSessionModel userSession) {
if (user.getOfflineUserSessions() == null) {
user.setOfflineUserSessions(new ArrayList<OfflineUserSessionEntity>());
}
if (getUserSessionEntityById(userSession.getUserSessionId()) != null) {
throw new ModelDuplicateException("User session already exists with id " + userSession.getUserSessionId() + " for user " + getMongoEntity().getUsername());
}
OfflineUserSessionEntity entity = new OfflineUserSessionEntity();
entity.setUserSessionId(userSession.getUserSessionId());
entity.setData(userSession.getData());
entity.setOfflineClientSessions(new ArrayList<OfflineClientSessionEntity>());
user.getOfflineUserSessions().add(entity);
updateUser();
}
@Override
public OfflineUserSessionModel getOfflineUserSession(String userSessionId) {
OfflineUserSessionEntity entity = getUserSessionEntityById(userSessionId);
return entity==null ? null : toModel(entity);
}
@Override
public Collection<OfflineUserSessionModel> getOfflineUserSessions() {
if (user.getOfflineUserSessions()==null) {
return Collections.emptyList();
} else {
List<OfflineUserSessionModel> result = new ArrayList<>();
for (OfflineUserSessionEntity entity : user.getOfflineUserSessions()) {
result.add(toModel(entity));
}
return result;
}
}
private OfflineUserSessionModel toModel(OfflineUserSessionEntity entity) {
OfflineUserSessionModel model = new OfflineUserSessionModel();
model.setUserSessionId(entity.getUserSessionId());
model.setData(entity.getData());
return model;
}
@Override
public boolean removeOfflineUserSession(String userSessionId) {
OfflineUserSessionEntity entity = getUserSessionEntityById(userSessionId);
if (entity != null) {
user.getOfflineUserSessions().remove(entity);
updateUser();
return true;
} else {
return false;
}
}
private OfflineUserSessionEntity getUserSessionEntityById(String userSessionId) {
if (user.getOfflineUserSessions() != null) {
for (OfflineUserSessionEntity entity : user.getOfflineUserSessions()) {
if (entity.getUserSessionId().equals(userSessionId)) {
return entity;
}
}
}
return null;
}
@Override
public void addOfflineClientSession(OfflineClientSessionModel clientSession) {
OfflineUserSessionEntity userSessionEntity = getUserSessionEntityById(clientSession.getUserSessionId());
if (userSessionEntity == null) {
throw new ModelException("OfflineUserSession with ID " + clientSession.getUserSessionId() + " doesn't exist for user " + getMongoEntity().getUsername());
}
OfflineClientSessionEntity clEntity = new OfflineClientSessionEntity();
clEntity.setClientSessionId(clientSession.getClientSessionId());
clEntity.setClientId(clientSession.getClientId());
clEntity.setData(clientSession.getData());
userSessionEntity.getOfflineClientSessions().add(clEntity);
updateUser();
}
@Override
public OfflineClientSessionModel getOfflineClientSession(String clientSessionId) {
if (user.getOfflineUserSessions() != null) {
for (OfflineUserSessionEntity userSession : user.getOfflineUserSessions()) {
for (OfflineClientSessionEntity clSession : userSession.getOfflineClientSessions()) {
if (clSession.getClientSessionId().equals(clientSessionId)) {
return toModel(clSession, userSession.getUserSessionId());
}
}
}
}
return null;
}
private OfflineClientSessionModel toModel(OfflineClientSessionEntity cls, String userSessionId) {
OfflineClientSessionModel model = new OfflineClientSessionModel();
model.setClientSessionId(cls.getClientSessionId());
model.setClientId(cls.getClientId());
model.setData(cls.getData());
model.setUserSessionId(userSessionId);
return model;
}
@Override
public Collection<OfflineClientSessionModel> getOfflineClientSessions() {
List<OfflineClientSessionModel> result = new ArrayList<>();
if (user.getOfflineUserSessions() != null) {
for (OfflineUserSessionEntity userSession : user.getOfflineUserSessions()) {
for (OfflineClientSessionEntity clSession : userSession.getOfflineClientSessions()) {
result.add(toModel(clSession, userSession.getUserSessionId()));
}
}
}
return result;
}
@Override
public boolean removeOfflineClientSession(String clientSessionId) {
if (user.getOfflineUserSessions() != null) {
for (OfflineUserSessionEntity userSession : user.getOfflineUserSessions()) {
for (OfflineClientSessionEntity clSession : userSession.getOfflineClientSessions()) {
if (clSession.getClientSessionId().equals(clientSessionId)) {
userSession.getOfflineClientSessions().remove(clSession);
updateUser();
return true;
}
}
}
}
return false;
}
@Override @Override
public boolean equals(Object o) { public boolean equals(Object o) {
if (this == o) return true; if (this == o) return true;

View file

@ -17,22 +17,18 @@
<dependency> <dependency>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<artifactId>keycloak-core</artifactId> <artifactId>keycloak-core</artifactId>
<scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<artifactId>keycloak-model-api</artifactId> <artifactId>keycloak-model-api</artifactId>
<scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<artifactId>keycloak-connections-infinispan</artifactId> <artifactId>keycloak-connections-infinispan</artifactId>
<scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.infinispan</groupId> <groupId>org.infinispan</groupId>
<artifactId>infinispan-core</artifactId> <artifactId>infinispan-core</artifactId>
<scope>provided</scope>
</dependency> </dependency>
</dependencies> </dependencies>
</project> </project>

View file

@ -14,6 +14,7 @@ import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map;
/** /**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a> * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@ -109,6 +110,11 @@ public class UserSessionAdapter implements UserSessionModel {
} }
} }
@Override
public Map<String, String> getNotes() {
return entity.getNotes();
}
@Override @Override
public State getState() { public State getState() {
return entity.getState(); return entity.getState();

View file

@ -10,6 +10,7 @@ import org.keycloak.models.sessions.infinispan.compat.entities.UserSessionEntity
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map;
/** /**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a> * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@ -144,5 +145,8 @@ public class UserSessionAdapter implements UserSessionModel {
} }
@Override
public Map<String, String> getNotes() {
return entity.getNotes();
}
} }

112
pom.xml
View file

@ -39,6 +39,7 @@
<io.netty.version>4.0.26.Final</io.netty.version> <io.netty.version>4.0.26.Final</io.netty.version>
<xnio.netty.netty-xnio-transport.version>0.1.1.Final</xnio.netty.netty-xnio-transport.version> <xnio.netty.netty-xnio-transport.version>0.1.1.Final</xnio.netty.netty-xnio-transport.version>
<hibernate.javax.persistence.version>1.0.0.Final</hibernate.javax.persistence.version> <hibernate.javax.persistence.version>1.0.0.Final</hibernate.javax.persistence.version>
<hibernate.javax.persistence.artifactId>hibernate-jpa-2.1-api</hibernate.javax.persistence.artifactId>
<hibernate.entitymanager.version>4.3.10.Final</hibernate.entitymanager.version> <hibernate.entitymanager.version>4.3.10.Final</hibernate.entitymanager.version>
<h2.version>1.4.187</h2.version> <h2.version>1.4.187</h2.version>
<mysql.version>5.1.29</mysql.version> <mysql.version>5.1.29</mysql.version>
@ -322,7 +323,7 @@
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.hibernate.javax.persistence</groupId> <groupId>org.hibernate.javax.persistence</groupId>
<artifactId>hibernate-jpa-2.1-api</artifactId> <artifactId>${hibernate.javax.persistence.artifactId}</artifactId>
<version>${hibernate.javax.persistence.version}</version> <version>${hibernate.javax.persistence.version}</version>
</dependency> </dependency>
<dependency> <dependency>
@ -1253,6 +1254,15 @@
<build> <build>
<pluginManagement> <pluginManagement>
<plugins> <plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-release-plugin</artifactId>
<version>2.5.2</version>
<configuration>
<autoVersionSubmodules>true</autoVersionSubmodules>
<tagNameFormat>@{project.version}</tagNameFormat>
</configuration>
</plugin>
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId> <artifactId>maven-surefire-plugin</artifactId>
@ -1349,6 +1359,48 @@
</build> </build>
<profiles> <profiles>
<profile>
<id>distribution</id>
<modules>
<module>distribution</module>
</modules>
</profile>
<profile>
<id>jboss-release</id>
<modules>
<module>docbook</module>
<module>distribution</module>
</modules>
</profile>
<profile>
<id>doclint-java8-disable</id>
<activation>
<jdk>[1.8,)</jdk>
</activation>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<configuration>
<additionalparam>-Xdoclint:none</additionalparam>
</configuration>
</plugin>
</plugins>
</build>
</profile>
<profile>
<id>arquillian-integration-tests</id>
<modules>
<module>distribution</module>
<module>testsuite/integration-arquillian</module>
</modules>
</profile>
<!-- Configure the JBoss Early Access Maven repository --> <!-- Configure the JBoss Early Access Maven repository -->
<profile> <profile>
<id>jboss-earlyaccess-repository</id> <id>jboss-earlyaccess-repository</id>
@ -1382,63 +1434,5 @@
</pluginRepository> </pluginRepository>
</pluginRepositories> </pluginRepositories>
</profile> </profile>
<profile>
<id>distribution</id>
<modules>
<module>distribution</module>
<module>testsuite/integration-arquillian</module>
</modules>
</profile>
<profile>
<id>jboss-release</id>
<modules>
<module>docbook</module>
<module>distribution</module>
<module>testsuite/integration-arquillian</module>
</modules>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<executions>
<execution>
<id>aggregate</id>
<phase>package</phase>
<goals>
<goal>aggregate</goal>
</goals>
<configuration>
<minmemory>128m</minmemory>
<maxmemory>1024m</maxmemory>
<aggregate>true</aggregate>
<excludePackageNames>
se.unlogic.*:com.restfully.*:org.jboss.resteasy.examples.*:org.jboss.resteasy.tests.*
</excludePackageNames>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
<profile>
<id>doclint-java8-disable</id>
<activation>
<jdk>[1.8,)</jdk>
</activation>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<configuration>
<additionalparam>-Xdoclint:none</additionalparam>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles> </profiles>
</project> </project>

View file

@ -27,8 +27,6 @@ import java.io.Serializable;
*/ */
public class CommonActionType implements Serializable { public class CommonActionType implements Serializable {
private static final long serialVersionUID = 1L;
protected String namespace; protected String namespace;
protected String value; protected String value;

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