keycloak-aplcache
Changes
core/src/main/java/org/keycloak/representations/idm/ClientInitialAccessCreatePresentation.java 36(+36 -0)
integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ClientInitialAccessResource.java 31(+31 -0)
integration/admin-client/src/main/java/org/keycloak/admin/client/resource/RealmResource.java 5(+4 -1)
model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/ClientAdapter.java 10(+5 -5)
model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedClient.java 8(+4 -4)
model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/ClientInitialAccessAdapter.java 69(+69 -0)
model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/compat/ClientInitialAccessAdapter.java 55(+55 -0)
model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/compat/entities/ClientInitialAccessEntity.java 68(+68 -0)
model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/compat/MemUserSessionProvider.java 65(+51 -14)
model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/compat/MemUserSessionProviderFactory.java 8(+4 -4)
model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/ClientInitialAccessEntity.java 48(+48 -0)
model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java 90(+69 -21)
model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/mapreduce/ClientInitialAccessMapper.java 80(+80 -0)
saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/clientregistration/EntityDescriptorClientRegistrationProvider.java 22(+3 -19)
services/src/main/java/org/keycloak/services/clientregistration/AdapterInstallationClientRegistrationProvider.java 5(+2 -3)
services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationAuth.java 116(+82 -34)
services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationProvider.java 3(+1 -2)
services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationService.java 2(+1 -1)
services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationTokenUtils.java 99(+99 -0)
services/src/main/java/org/keycloak/services/clientregistration/DefaultClientRegistrationProvider.java 31(+21 -10)
services/src/main/java/org/keycloak/services/clientregistration/oidc/OIDCClientRegistrationProvider.java 25(+4 -21)
services/src/main/java/org/keycloak/services/resources/admin/ClientInitialAccessResource.java 102(+102 -0)
testsuite/integration/src/test/java/org/keycloak/testsuite/admin/ClientInitialAccessTest.java 58(+58 -0)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/AbstractClientRegistrationTest.java 30(+22 -8)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/ClientRegistrationTest.java 16(+0 -16)
Details
diff --git a/client-api/src/main/java/org/keycloak/client/registration/Auth.java b/client-api/src/main/java/org/keycloak/client/registration/Auth.java
index 5b0e85f..31ad4e8 100644
--- a/client-api/src/main/java/org/keycloak/client/registration/Auth.java
+++ b/client-api/src/main/java/org/keycloak/client/registration/Auth.java
@@ -3,6 +3,7 @@ package org.keycloak.client.registration;
import org.apache.http.HttpHeaders;
import org.apache.http.HttpRequest;
import org.keycloak.common.util.Base64;
+import org.keycloak.representations.idm.ClientInitialAccessPresentation;
import org.keycloak.representations.idm.ClientRepresentation;
/**
@@ -16,6 +17,11 @@ public abstract class Auth {
return new BearerTokenAuth(token);
}
+ public static Auth token(ClientInitialAccessPresentation initialAccess) {
+ return new BearerTokenAuth(initialAccess.getToken());
+ }
+
+
public static Auth token(ClientRepresentation client) {
return new BearerTokenAuth(client.getRegistrationAccessToken());
}
diff --git a/client-api/src/main/java/org/keycloak/client/registration/ClientRegistration.java b/client-api/src/main/java/org/keycloak/client/registration/ClientRegistration.java
index f932226..3be763f 100644
--- a/client-api/src/main/java/org/keycloak/client/registration/ClientRegistration.java
+++ b/client-api/src/main/java/org/keycloak/client/registration/ClientRegistration.java
@@ -2,6 +2,7 @@ package org.keycloak.client.registration;
import org.apache.http.client.HttpClient;
import org.apache.http.impl.client.HttpClients;
+import org.codehaus.jackson.map.ObjectMapper;
import org.keycloak.representations.adapters.config.AdapterConfig;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.util.JsonSerialization;
@@ -14,6 +15,11 @@ import java.io.InputStream;
*/
public class ClientRegistration {
+ public static final ObjectMapper outputMapper = new ObjectMapper();
+ static {
+ outputMapper.getSerializationConfig().addMixInAnnotations(ClientRepresentation.class, ClientRepresentationMixIn.class);
+ }
+
private final String DEFAULT = "default";
private final String INSTALLATION = "install";
@@ -69,15 +75,16 @@ public class ClientRegistration {
httpUtil.doDelete(DEFAULT, clientId);
}
- private String serialize(ClientRepresentation client) throws ClientRegistrationException {
+ public static String serialize(ClientRepresentation client) throws ClientRegistrationException {
try {
- return JsonSerialization.writeValueAsString(client);
+
+ return outputMapper.writeValueAsString(client);
} catch (IOException e) {
throw new ClientRegistrationException("Failed to write json object", e);
}
}
- private <T> T deserialize(InputStream inputStream, Class<T> clazz) throws ClientRegistrationException {
+ private static <T> T deserialize(InputStream inputStream, Class<T> clazz) throws ClientRegistrationException {
try {
return JsonSerialization.readValue(inputStream, clazz);
} catch (IOException e) {
diff --git a/client-api/src/main/java/org/keycloak/client/registration/ClientRepresentationMixIn.java b/client-api/src/main/java/org/keycloak/client/registration/ClientRepresentationMixIn.java
new file mode 100644
index 0000000..ba382f6
--- /dev/null
+++ b/client-api/src/main/java/org/keycloak/client/registration/ClientRepresentationMixIn.java
@@ -0,0 +1,13 @@
+package org.keycloak.client.registration;
+
+import org.codehaus.jackson.annotate.JsonIgnore;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+abstract class ClientRepresentationMixIn {
+
+ @JsonIgnore
+ String registrationAccessToken;
+
+}
diff --git a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.7.0.xml b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.7.0.xml
index aed99fc..c53eb4b 100755
--- a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.7.0.xml
+++ b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.7.0.xml
@@ -59,7 +59,7 @@
<addForeignKeyConstraint baseColumnNames="ROLE_ID" baseTableName="GROUP_ROLE_MAPPING" constraintName="FK_GROUP_ROLE_ROLE" referencedColumnNames="ID" referencedTableName="KEYCLOAK_ROLE"/>
<addColumn tableName="CLIENT">
- <column name="REGISTRATION_SECRET" type="VARCHAR(255)"/>
+ <column name="REGISTRATION_TOKEN" type="VARCHAR(255)"/>
</addColumn>
</changeSet>
diff --git a/core/src/main/java/org/keycloak/representations/idm/ClientInitialAccessCreatePresentation.java b/core/src/main/java/org/keycloak/representations/idm/ClientInitialAccessCreatePresentation.java
new file mode 100644
index 0000000..4c18b3d
--- /dev/null
+++ b/core/src/main/java/org/keycloak/representations/idm/ClientInitialAccessCreatePresentation.java
@@ -0,0 +1,36 @@
+package org.keycloak.representations.idm;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class ClientInitialAccessCreatePresentation {
+
+ private Integer expiration;
+
+ private Integer count;
+
+ public ClientInitialAccessCreatePresentation() {
+ }
+
+ public ClientInitialAccessCreatePresentation(Integer expiration, Integer count) {
+ this.expiration = expiration;
+ this.count = count;
+ }
+
+ public Integer getExpiration() {
+ return expiration;
+ }
+
+ public void setExpiration(Integer expiration) {
+ this.expiration = expiration;
+ }
+
+ public Integer getCount() {
+ return count;
+ }
+
+ public void setCount(Integer count) {
+ this.count = count;
+ }
+
+}
diff --git a/core/src/main/java/org/keycloak/representations/idm/ClientInitialAccessPresentation.java b/core/src/main/java/org/keycloak/representations/idm/ClientInitialAccessPresentation.java
new file mode 100644
index 0000000..d8021ad
--- /dev/null
+++ b/core/src/main/java/org/keycloak/representations/idm/ClientInitialAccessPresentation.java
@@ -0,0 +1,67 @@
+package org.keycloak.representations.idm;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class ClientInitialAccessPresentation {
+
+ private String id;
+
+ private String token;
+
+ private Integer timestamp;
+
+ private Integer expiration;
+
+ private Integer count;
+
+ private Integer remainingCount;
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public String getToken() {
+ return token;
+ }
+
+ public void setToken(String token) {
+ this.token = token;
+ }
+
+ public Integer getTimestamp() {
+ return timestamp;
+ }
+
+ public void setTimestamp(Integer timestamp) {
+ this.timestamp = timestamp;
+ }
+
+ public Integer getExpiration() {
+ return expiration;
+ }
+
+ public void setExpiration(Integer expiration) {
+ this.expiration = expiration;
+ }
+
+ public Integer getCount() {
+ return count;
+ }
+
+ public void setCount(Integer count) {
+ this.count = count;
+ }
+
+ public Integer getRemainingCount() {
+ return remainingCount;
+ }
+
+ public void setRemainingCount(Integer remainingCount) {
+ this.remainingCount = remainingCount;
+ }
+}
diff --git a/core/src/main/java/org/keycloak/util/TokenUtil.java b/core/src/main/java/org/keycloak/util/TokenUtil.java
index 0d103a6..10b6c20 100644
--- a/core/src/main/java/org/keycloak/util/TokenUtil.java
+++ b/core/src/main/java/org/keycloak/util/TokenUtil.java
@@ -19,6 +19,7 @@ public class TokenUtil {
public static final String TOKEN_TYPE_OFFLINE = "Offline";
+
public static boolean isOfflineTokenRequested(String scopeParam) {
if (scopeParam == null) {
return false;
diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ClientInitialAccessResource.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ClientInitialAccessResource.java
new file mode 100644
index 0000000..8875a4c
--- /dev/null
+++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ClientInitialAccessResource.java
@@ -0,0 +1,31 @@
+package org.keycloak.admin.client.resource;
+
+import org.keycloak.representations.idm.ClientInitialAccessCreatePresentation;
+import org.keycloak.representations.idm.ClientInitialAccessPresentation;
+
+import javax.ws.rs.*;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriInfo;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public interface ClientInitialAccessResource {
+
+ @POST
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Produces(MediaType.APPLICATION_JSON)
+ ClientInitialAccessPresentation create(ClientInitialAccessCreatePresentation rep);
+
+ @GET
+ @Produces(MediaType.APPLICATION_JSON)
+ List<ClientInitialAccessPresentation> list();
+
+ @DELETE
+ @Path("{id}")
+ void delete(final @PathParam("id") String id);
+
+}
diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/RealmResource.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/RealmResource.java
index 87513ec..82b023b 100644
--- a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/RealmResource.java
+++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/RealmResource.java
@@ -45,5 +45,8 @@ public interface RealmResource {
@Path("client-session-stats")
@GET
List<Map<String, String>> getClientSessionStats();
-
+
+ @Path("clients-initial-access")
+ ClientInitialAccessResource clientInitialAccess();
+
}
diff --git a/model/api/src/main/java/org/keycloak/models/ClientInitialAccessModel.java b/model/api/src/main/java/org/keycloak/models/ClientInitialAccessModel.java
new file mode 100755
index 0000000..7447319
--- /dev/null
+++ b/model/api/src/main/java/org/keycloak/models/ClientInitialAccessModel.java
@@ -0,0 +1,22 @@
+package org.keycloak.models;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public interface ClientInitialAccessModel {
+
+ String getId();
+
+ RealmModel getRealm();
+
+ int getTimestamp();
+
+ int getExpiration();
+
+ int getCount();
+
+ int getRemainingCount();
+
+ void decreaseRemainingCount();
+
+}
diff --git a/model/api/src/main/java/org/keycloak/models/ClientModel.java b/model/api/src/main/java/org/keycloak/models/ClientModel.java
index c421aea..051493e 100755
--- a/model/api/src/main/java/org/keycloak/models/ClientModel.java
+++ b/model/api/src/main/java/org/keycloak/models/ClientModel.java
@@ -90,8 +90,8 @@ public interface ClientModel extends RoleContainerModel {
String getSecret();
public void setSecret(String secret);
- String getRegistrationSecret();
- void setRegistrationSecret(String registrationSecret);
+ String getRegistrationToken();
+ void setRegistrationToken(String registrationToken);
boolean isFullScopeAllowed();
void setFullScopeAllowed(boolean value);
diff --git a/model/api/src/main/java/org/keycloak/models/entities/ClientEntity.java b/model/api/src/main/java/org/keycloak/models/entities/ClientEntity.java
index f15614f..d04699f 100755
--- a/model/api/src/main/java/org/keycloak/models/entities/ClientEntity.java
+++ b/model/api/src/main/java/org/keycloak/models/entities/ClientEntity.java
@@ -17,7 +17,7 @@ public class ClientEntity extends AbstractIdentifiableEntity {
private boolean enabled;
private String clientAuthenticatorType;
private String secret;
- private String registrationSecret;
+ private String registrationToken;
private String protocol;
private int notBefore;
private boolean publicClient;
@@ -91,12 +91,12 @@ public class ClientEntity extends AbstractIdentifiableEntity {
this.secret = secret;
}
- public String getRegistrationSecret() {
- return registrationSecret;
+ public String getRegistrationToken() {
+ return registrationToken;
}
- public void setRegistrationSecret(String registrationSecret) {
- this.registrationSecret = registrationSecret;
+ public void setRegistrationToken(String registrationToken) {
+ this.registrationToken = registrationToken;
}
public int getNotBefore() {
diff --git a/model/api/src/main/java/org/keycloak/models/UserSessionProvider.java b/model/api/src/main/java/org/keycloak/models/UserSessionProvider.java
index 1a59f4f..0c1df9c 100755
--- a/model/api/src/main/java/org/keycloak/models/UserSessionProvider.java
+++ b/model/api/src/main/java/org/keycloak/models/UserSessionProvider.java
@@ -63,6 +63,11 @@ public interface UserSessionProvider extends Provider {
UserSessionModel importUserSession(UserSessionModel persistentUserSession, boolean offline);
ClientSessionModel importClientSession(ClientSessionModel persistentClientSession, boolean offline);
+ ClientInitialAccessModel createClientInitialAccessModel(RealmModel realm, int expiration, int count);
+ ClientInitialAccessModel getClientInitialAccessModel(RealmModel realm, String id);
+ void removeClientInitialAccessModel(RealmModel realm, String id);
+ List<ClientInitialAccessModel> listClientInitialAccess(RealmModel realm);
+
void close();
}
diff --git a/model/api/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java b/model/api/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java
index c35c58e..02caa74 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java
@@ -43,8 +43,6 @@ import java.util.UUID;
*/
public final class KeycloakModelUtils {
- private static final int RANDOM_PASSWORD_BYTES = 32;
-
private KeycloakModelUtils() {
}
@@ -182,16 +180,6 @@ public final class KeycloakModelUtils {
return secret;
}
- public static void generateRegistrationAccessToken(ClientModel client) {
- client.setRegistrationSecret(generatePassword());
- }
-
- public static String generatePassword() {
- byte[] buf = new byte[RANDOM_PASSWORD_BYTES];
- new SecureRandom().nextBytes(buf);
- return Base64Url.encode(buf);
- }
-
public static String getDefaultClientAuthenticatorType() {
return "client-secret";
}
diff --git a/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java b/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
index 0df3516..3073616 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
@@ -387,7 +387,6 @@ public class ModelToRepresentation {
rep.setNotBefore(clientModel.getNotBefore());
rep.setNodeReRegistrationTimeout(clientModel.getNodeReRegistrationTimeout());
rep.setClientAuthenticatorType(clientModel.getClientAuthenticatorType());
- rep.setRegistrationAccessToken(clientModel.getRegistrationSecret());
Set<String> redirectUris = clientModel.getRedirectUris();
if (redirectUris != null) {
diff --git a/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java b/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
index a46ee57..a31d355 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
@@ -737,8 +737,6 @@ public class RepresentationToModel {
KeycloakModelUtils.generateSecret(client);
}
- client.setRegistrationSecret(resourceRep.getRegistrationAccessToken());
-
if (resourceRep.getAttributes() != null) {
for (Map.Entry<String, String> entry : resourceRep.getAttributes().entrySet()) {
client.setAttribute(entry.getKey(), entry.getValue());
@@ -815,7 +813,6 @@ public class RepresentationToModel {
if (rep.isSurrogateAuthRequired() != null) resource.setSurrogateAuthRequired(rep.isSurrogateAuthRequired());
if (rep.getNodeReRegistrationTimeout() != null) resource.setNodeReRegistrationTimeout(rep.getNodeReRegistrationTimeout());
if (rep.getClientAuthenticatorType() != null) resource.setClientAuthenticatorType(rep.getClientAuthenticatorType());
- if (rep.getRegistrationAccessToken() != null) resource.setRegistrationSecret(rep.getRegistrationAccessToken());
resource.updateClient();
if (rep.getProtocol() != null) resource.setProtocol(rep.getProtocol());
diff --git a/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/ClientAdapter.java b/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/ClientAdapter.java
index 7a87373..e03452a 100755
--- a/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/ClientAdapter.java
+++ b/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/ClientAdapter.java
@@ -120,14 +120,14 @@ public class ClientAdapter implements ClientModel {
getDelegateForUpdate();
updated.setSecret(secret);
}
- public String getRegistrationSecret() {
- if (updated != null) return updated.getRegistrationSecret();
- return cached.getRegistrationSecret();
+ public String getRegistrationToken() {
+ if (updated != null) return updated.getRegistrationToken();
+ return cached.getRegistrationToken();
}
- public void setRegistrationSecret(String registrationsecret) {
+ public void setRegistrationToken(String registrationToken) {
getDelegateForUpdate();
- updated.setRegistrationSecret(registrationsecret);
+ updated.setRegistrationToken(registrationToken);
}
public boolean isPublicClient() {
diff --git a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedClient.java b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedClient.java
index 1c04b9d..7c7b97d 100755
--- a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedClient.java
+++ b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedClient.java
@@ -31,7 +31,7 @@ public class CachedClient implements Serializable {
private boolean enabled;
private String clientAuthenticatorType;
private String secret;
- private String registrationSecret;
+ private String registrationToken;
private String protocol;
private Map<String, String> attributes = new HashMap<String, String>();
private boolean publicClient;
@@ -58,7 +58,7 @@ public class CachedClient implements Serializable {
id = model.getId();
clientAuthenticatorType = model.getClientAuthenticatorType();
secret = model.getSecret();
- registrationSecret = model.getRegistrationSecret();
+ registrationToken = model.getRegistrationToken();
clientId = model.getClientId();
name = model.getName();
description = model.getDescription();
@@ -131,8 +131,8 @@ public class CachedClient implements Serializable {
return secret;
}
- public String getRegistrationSecret() {
- return registrationSecret;
+ public String getRegistrationToken() {
+ return registrationToken;
}
public boolean isPublicClient() {
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/ClientAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/ClientAdapter.java
index 5ea0b11..a7dc0f4 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/ClientAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/ClientAdapter.java
@@ -178,13 +178,13 @@ public class ClientAdapter implements ClientModel {
}
@Override
- public String getRegistrationSecret() {
- return entity.getRegistrationSecret();
+ public String getRegistrationToken() {
+ return entity.getRegistrationToken();
}
@Override
- public void setRegistrationSecret(String registrationSecret) {
- entity.setRegistrationSecret(registrationSecret);
+ public void setRegistrationToken(String registrationToken) {
+ entity.setRegistrationToken(registrationToken);
}
@Override
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientEntity.java
index 881b129..6218e26 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientEntity.java
@@ -42,8 +42,8 @@ public class ClientEntity {
private boolean enabled;
@Column(name="SECRET")
private String secret;
- @Column(name="REGISTRATION_SECRET")
- private String registrationSecret;
+ @Column(name="REGISTRATION_TOKEN")
+ private String registrationToken;
@Column(name="CLIENT_AUTHENTICATOR_TYPE")
private String clientAuthenticatorType;
@Column(name="NOT_BEFORE")
@@ -203,12 +203,12 @@ public class ClientEntity {
this.secret = secret;
}
- public String getRegistrationSecret() {
- return registrationSecret;
+ public String getRegistrationToken() {
+ return registrationToken;
}
- public void setRegistrationSecret(String registrationSecret) {
- this.registrationSecret = registrationSecret;
+ public void setRegistrationToken(String registrationToken) {
+ this.registrationToken = registrationToken;
}
public int getNotBefore() {
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ClientAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ClientAdapter.java
index cbacd09..8eb562f 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ClientAdapter.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ClientAdapter.java
@@ -178,13 +178,13 @@ public class ClientAdapter extends AbstractMongoAdapter<MongoClientEntity> imple
}
@Override
- public String getRegistrationSecret() {
- return getMongoEntity().getRegistrationSecret();
+ public String getRegistrationToken() {
+ return getMongoEntity().getRegistrationToken();
}
@Override
- public void setRegistrationSecret(String registrationSecretsecret) {
- getMongoEntity().setRegistrationSecret(registrationSecretsecret);
+ public void setRegistrationToken(String registrationToken) {
+ getMongoEntity().setRegistrationToken(registrationToken);
updateMongoEntity();
}
diff --git a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/ClientInitialAccessAdapter.java b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/ClientInitialAccessAdapter.java
new file mode 100644
index 0000000..7d75335
--- /dev/null
+++ b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/ClientInitialAccessAdapter.java
@@ -0,0 +1,69 @@
+package org.keycloak.models.sessions.infinispan;
+
+import org.infinispan.Cache;
+import org.keycloak.models.ClientInitialAccessModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.sessions.infinispan.entities.ClientInitialAccessEntity;
+import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class ClientInitialAccessAdapter implements ClientInitialAccessModel {
+
+ private final KeycloakSession session;
+ private final InfinispanUserSessionProvider provider;
+ private final Cache<String, SessionEntity> cache;
+ private final RealmModel realm;
+ private final ClientInitialAccessEntity entity;
+
+ public ClientInitialAccessAdapter(KeycloakSession session, InfinispanUserSessionProvider provider, Cache<String, SessionEntity> cache, RealmModel realm, ClientInitialAccessEntity entity) {
+ this.session = session;
+ this.provider = provider;
+ this.cache = cache;
+ this.realm = realm;
+ this.entity = entity;
+ }
+
+ @Override
+ public String getId() {
+ return entity.getId();
+ }
+
+ @Override
+ public RealmModel getRealm() {
+ return realm;
+ }
+
+ @Override
+ public int getTimestamp() {
+ return entity.getTimestamp();
+ }
+
+ @Override
+ public int getExpiration() {
+ return entity.getExpiration();
+ }
+
+ @Override
+ public int getCount() {
+ return entity.getCount();
+ }
+
+ @Override
+ public int getRemainingCount() {
+ return entity.getRemainingCount();
+ }
+
+ @Override
+ public void decreaseRemainingCount() {
+ entity.setRemainingCount(entity.getRemainingCount() - 1);
+ update();
+ }
+
+ void update() {
+ provider.getTx().replace(cache, entity.getId(), entity);
+ }
+
+}
diff --git a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/compat/ClientInitialAccessAdapter.java b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/compat/ClientInitialAccessAdapter.java
new file mode 100644
index 0000000..3398eff
--- /dev/null
+++ b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/compat/ClientInitialAccessAdapter.java
@@ -0,0 +1,55 @@
+package org.keycloak.models.sessions.infinispan.compat;
+
+import org.keycloak.models.ClientInitialAccessModel;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.sessions.infinispan.compat.entities.ClientInitialAccessEntity;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class ClientInitialAccessAdapter implements ClientInitialAccessModel {
+
+ private final RealmModel realm;
+ private final ClientInitialAccessEntity entity;
+
+ public ClientInitialAccessAdapter(RealmModel realm, ClientInitialAccessEntity entity) {
+ this.realm = realm;
+ this.entity = entity;
+ }
+
+ @Override
+ public String getId() {
+ return entity.getId();
+ }
+
+ @Override
+ public RealmModel getRealm() {
+ return realm;
+ }
+
+ @Override
+ public int getTimestamp() {
+ return entity.getTimestamp();
+ }
+
+ @Override
+ public int getExpiration() {
+ return entity.getExpires();
+ }
+
+ @Override
+ public int getCount() {
+ return entity.getCount();
+ }
+
+ @Override
+ public int getRemainingCount() {
+ return entity.getRemainingCount();
+ }
+
+ @Override
+ public void decreaseRemainingCount() {
+ entity.setRemainingCount(entity.getRemainingCount() - 1);
+ }
+
+}
diff --git a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/compat/entities/ClientInitialAccessEntity.java b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/compat/entities/ClientInitialAccessEntity.java
new file mode 100644
index 0000000..ed0aeac
--- /dev/null
+++ b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/compat/entities/ClientInitialAccessEntity.java
@@ -0,0 +1,68 @@
+package org.keycloak.models.sessions.infinispan.compat.entities;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class ClientInitialAccessEntity {
+
+ private String id;
+
+ private String realmId;
+
+ private int timestamp;
+
+ private int expires;
+
+ private int count;
+
+ private int remainingCount;
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public String getRealmId() {
+ return realmId;
+ }
+
+ public void setRealmId(String realmId) {
+ this.realmId = realmId;
+ }
+
+ public int getTimestamp() {
+ return timestamp;
+ }
+
+ public void setTimestamp(int timestamp) {
+ this.timestamp = timestamp;
+ }
+
+ public int getExpires() {
+ return expires;
+ }
+
+ public void setExpiration(int expires) {
+ this.expires = expires;
+ }
+
+ public int getCount() {
+ return count;
+ }
+
+ public void setCount(int count) {
+ this.count = count;
+ }
+
+ public int getRemainingCount() {
+ return remainingCount;
+ }
+
+ public void setRemainingCount(int remainingCount) {
+ this.remainingCount = remainingCount;
+ }
+
+}
diff --git a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/compat/MemUserSessionProvider.java b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/compat/MemUserSessionProvider.java
index f45edf1..db20ef8 100755
--- a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/compat/MemUserSessionProvider.java
+++ b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/compat/MemUserSessionProvider.java
@@ -1,19 +1,8 @@
package org.keycloak.models.sessions.infinispan.compat;
-import org.keycloak.models.ClientModel;
-import org.keycloak.models.ClientSessionModel;
-import org.keycloak.models.KeycloakSession;
-import org.keycloak.models.ModelDuplicateException;
-import org.keycloak.models.RealmModel;
-import org.keycloak.models.UserModel;
-import org.keycloak.models.UserSessionModel;
-import org.keycloak.models.UserSessionProvider;
-import org.keycloak.models.UsernameLoginFailureModel;
+import org.keycloak.models.*;
import org.keycloak.models.session.UserSessionPersisterProvider;
-import org.keycloak.models.sessions.infinispan.compat.entities.ClientSessionEntity;
-import org.keycloak.models.sessions.infinispan.compat.entities.UserSessionEntity;
-import org.keycloak.models.sessions.infinispan.compat.entities.UsernameLoginFailureEntity;
-import org.keycloak.models.sessions.infinispan.compat.entities.UsernameLoginFailureKey;
+import org.keycloak.models.sessions.infinispan.compat.entities.*;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.models.utils.RealmInfoUtil;
import org.keycloak.common.util.Time;
@@ -41,11 +30,12 @@ public class MemUserSessionProvider implements UserSessionProvider {
private final ConcurrentHashMap<String, UserSessionEntity> offlineUserSessions;
private final ConcurrentHashMap<String, ClientSessionEntity> offlineClientSessions;
+ private ConcurrentHashMap<String, ClientInitialAccessEntity> clientInitialAccess;
public MemUserSessionProvider(KeycloakSession session, ConcurrentHashMap<String, UserSessionEntity> userSessions, ConcurrentHashMap<String, String> userSessionsByBrokerSessionId,
ConcurrentHashMap<String, Set<String>> userSessionsByBrokerUserId, ConcurrentHashMap<String, ClientSessionEntity> clientSessions,
ConcurrentHashMap<UsernameLoginFailureKey, UsernameLoginFailureEntity> loginFailures,
- ConcurrentHashMap<String, UserSessionEntity> offlineUserSessions, ConcurrentHashMap<String, ClientSessionEntity> offlineClientSessions) {
+ ConcurrentHashMap<String, UserSessionEntity> offlineUserSessions, ConcurrentHashMap<String, ClientSessionEntity> offlineClientSessions, ConcurrentHashMap<String, ClientInitialAccessEntity> clientInitialAccess) {
this.session = session;
this.userSessions = userSessions;
this.clientSessions = clientSessions;
@@ -54,6 +44,7 @@ public class MemUserSessionProvider implements UserSessionProvider {
this.userSessionsByBrokerUserId = userSessionsByBrokerUserId;
this.offlineUserSessions = offlineUserSessions;
this.offlineClientSessions = offlineClientSessions;
+ this.clientInitialAccess = clientInitialAccess;
}
@Override
@@ -341,6 +332,15 @@ public class MemUserSessionProvider implements UserSessionProvider {
persister.removeClientSession(s.getId(), true);
}
}
+
+ // Remove expired initial access
+ Iterator<ClientInitialAccessEntity> iaitr = clientInitialAccess.values().iterator();
+ while (iaitr.hasNext()) {
+ ClientInitialAccessEntity e = iaitr.next();
+ if (e.getRealmId().equals(realm.getId()) && (e.getExpires() < Time.currentTime())) {
+ iaitr.remove();
+ }
+ }
}
@Override
@@ -575,6 +575,43 @@ public class MemUserSessionProvider implements UserSessionProvider {
}
@Override
+ public ClientInitialAccessModel createClientInitialAccessModel(RealmModel realm, int expiration, int count) {
+ String id = KeycloakModelUtils.generateId();
+
+ ClientInitialAccessEntity entity = new ClientInitialAccessEntity();
+ entity.setId(id);
+ entity.setRealmId(realm.getId());
+ entity.setTimestamp(Time.currentTime());
+ entity.setExpiration(expiration);
+ entity.setCount(count);
+ entity.setRemainingCount(count);
+
+ clientInitialAccess.put(id, entity);
+
+ return new ClientInitialAccessAdapter(realm, entity);
+ }
+
+ @Override
+ public ClientInitialAccessModel getClientInitialAccessModel(RealmModel realm, String id) {
+ ClientInitialAccessEntity entity = clientInitialAccess.get(id);
+ return entity != null ? new ClientInitialAccessAdapter(realm, entity) : null;
+ }
+
+ @Override
+ public void removeClientInitialAccessModel(RealmModel realm, String id) {
+ clientInitialAccess.remove(id);
+ }
+
+ @Override
+ public List<ClientInitialAccessModel> listClientInitialAccess(RealmModel realm) {
+ List<ClientInitialAccessModel> models = new LinkedList<>();
+ for (ClientInitialAccessEntity e : clientInitialAccess.values()) {
+ models.add(new ClientInitialAccessAdapter(realm, e));
+ }
+ return models;
+ }
+
+ @Override
public void close() {
}
diff --git a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/compat/MemUserSessionProviderFactory.java b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/compat/MemUserSessionProviderFactory.java
index 187a33f..451fcb0 100644
--- a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/compat/MemUserSessionProviderFactory.java
+++ b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/compat/MemUserSessionProviderFactory.java
@@ -1,14 +1,12 @@
package org.keycloak.models.sessions.infinispan.compat;
-import org.keycloak.Config;
import org.keycloak.models.KeycloakSession;
-import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.UserSessionProvider;
-import org.keycloak.models.UserSessionProviderFactory;
import org.keycloak.models.sessions.infinispan.compat.entities.ClientSessionEntity;
import org.keycloak.models.sessions.infinispan.compat.entities.UserSessionEntity;
import org.keycloak.models.sessions.infinispan.compat.entities.UsernameLoginFailureEntity;
import org.keycloak.models.sessions.infinispan.compat.entities.UsernameLoginFailureKey;
+import org.keycloak.models.sessions.infinispan.compat.entities.ClientInitialAccessEntity;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
@@ -29,9 +27,11 @@ public class MemUserSessionProviderFactory {
private ConcurrentHashMap<String, UserSessionEntity> offlineUserSessions = new ConcurrentHashMap<String, UserSessionEntity>();
private ConcurrentHashMap<String, ClientSessionEntity> offlineClientSessions = new ConcurrentHashMap<String, ClientSessionEntity>();
+ private ConcurrentHashMap<String, ClientInitialAccessEntity> clientInitialAccess = new ConcurrentHashMap<>();
+
public UserSessionProvider create(KeycloakSession session) {
return new MemUserSessionProvider(session, userSessions, userSessionsByBrokerSessionId, userSessionsByBrokerUserId, clientSessions, loginFailures,
- offlineUserSessions, offlineClientSessions);
+ offlineUserSessions, offlineClientSessions, clientInitialAccess);
}
public void close() {
diff --git a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/ClientInitialAccessEntity.java b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/ClientInitialAccessEntity.java
new file mode 100644
index 0000000..05daf1e
--- /dev/null
+++ b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/ClientInitialAccessEntity.java
@@ -0,0 +1,48 @@
+package org.keycloak.models.sessions.infinispan.entities;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class ClientInitialAccessEntity extends SessionEntity {
+
+ private int timestamp;
+
+ private int expires;
+
+ private int count;
+
+ private int remainingCount;
+
+ public int getTimestamp() {
+ return timestamp;
+ }
+
+ public void setTimestamp(int timestamp) {
+ this.timestamp = timestamp;
+ }
+
+ public int getExpiration() {
+ return expires;
+ }
+
+ public void setExpiration(int expires) {
+ this.expires = expires;
+ }
+
+ public int getCount() {
+ return count;
+ }
+
+ public void setCount(int count) {
+ this.count = count;
+ }
+
+ public int getRemainingCount() {
+ return remainingCount;
+ }
+
+ public void setRemainingCount(int remainingCount) {
+ this.remainingCount = remainingCount;
+ }
+
+}
diff --git a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java
index 9ba1587..c819259 100755
--- a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java
+++ b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java
@@ -3,28 +3,10 @@ package org.keycloak.models.sessions.infinispan;
import org.infinispan.Cache;
import org.infinispan.distexec.mapreduce.MapReduceTask;
import org.jboss.logging.Logger;
-import org.keycloak.models.ClientModel;
-import org.keycloak.models.ClientSessionModel;
-import org.keycloak.models.KeycloakSession;
-import org.keycloak.models.KeycloakTransaction;
-import org.keycloak.models.RealmModel;
-import org.keycloak.models.UserModel;
-import org.keycloak.models.UserSessionModel;
-import org.keycloak.models.UserSessionProvider;
-import org.keycloak.models.UsernameLoginFailureModel;
+import org.keycloak.models.*;
import org.keycloak.models.session.UserSessionPersisterProvider;
-import org.keycloak.models.sessions.infinispan.entities.ClientSessionEntity;
-import org.keycloak.models.sessions.infinispan.entities.LoginFailureEntity;
-import org.keycloak.models.sessions.infinispan.entities.LoginFailureKey;
-import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
-import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity;
-import org.keycloak.models.sessions.infinispan.mapreduce.ClientSessionMapper;
-import org.keycloak.models.sessions.infinispan.mapreduce.FirstResultReducer;
-import org.keycloak.models.sessions.infinispan.mapreduce.LargestResultReducer;
-import org.keycloak.models.sessions.infinispan.mapreduce.SessionMapper;
-import org.keycloak.models.sessions.infinispan.mapreduce.UserLoginFailureMapper;
-import org.keycloak.models.sessions.infinispan.mapreduce.UserSessionMapper;
-import org.keycloak.models.sessions.infinispan.mapreduce.UserSessionNoteMapper;
+import org.keycloak.models.sessions.infinispan.entities.*;
+import org.keycloak.models.sessions.infinispan.mapreduce.*;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.models.utils.RealmInfoUtil;
import org.keycloak.common.util.Time;
@@ -355,6 +337,15 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
persister.removeClientSession(clientSessionId, true);
}
+ // Remove expired client initial access
+ map = new MapReduceTask(sessionCache)
+ .mappedWith(ClientInitialAccessMapper.create(realm.getId()).time(Time.currentTime()).remainingCount(0).emitKey())
+ .reducedWith(new FirstResultReducer())
+ .execute();
+
+ for (String id : map.keySet()) {
+ tx.remove(sessionCache, id);
+ }
}
@Override
@@ -538,11 +529,24 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
return models;
}
+ List<ClientInitialAccessModel> wrapClientInitialAccess(RealmModel realm, Collection<ClientInitialAccessEntity> entities) {
+ List<ClientInitialAccessModel> models = new LinkedList<>();
+ for (ClientInitialAccessEntity e : entities) {
+ models.add(wrap(realm, e));
+ }
+ return models;
+ }
+
ClientSessionAdapter wrap(RealmModel realm, ClientSessionEntity entity, boolean offline) {
Cache<String, SessionEntity> cache = getCache(offline);
return entity != null ? new ClientSessionAdapter(session, this, cache, realm, entity, offline) : null;
}
+ ClientInitialAccessAdapter wrap(RealmModel realm, ClientInitialAccessEntity entity) {
+ Cache<String, SessionEntity> cache = getCache(false);
+ return entity != null ? new ClientInitialAccessAdapter(session, this, cache, realm, entity) : null;
+ }
+
UsernameLoginFailureModel wrap(LoginFailureKey key, LoginFailureEntity entity) {
return entity != null ? new UsernameLoginFailureAdapter(this, loginFailureCache, key, entity) : null;
@@ -680,6 +684,50 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
return wrap(clientSession.getRealm(), entity, offline);
}
+ @Override
+ public ClientInitialAccessModel createClientInitialAccessModel(RealmModel realm, int expiration, int count) {
+ String id = KeycloakModelUtils.generateId();
+
+ ClientInitialAccessEntity entity = new ClientInitialAccessEntity();
+ entity.setId(id);
+ entity.setRealm(realm.getId());
+ entity.setTimestamp(Time.currentTime());
+ entity.setExpiration(expiration);
+ entity.setCount(count);
+ entity.setRemainingCount(count);
+
+ tx.put(sessionCache, id, entity);
+
+ return wrap(realm, entity);
+ }
+
+ @Override
+ public ClientInitialAccessModel getClientInitialAccessModel(RealmModel realm, String id) {
+ Cache<String, SessionEntity> cache = getCache(false);
+ ClientInitialAccessEntity entity = (ClientInitialAccessEntity) cache.get(id);
+
+ // If created in this transaction
+ if (entity == null) {
+ entity = (ClientInitialAccessEntity) tx.get(cache, id);
+ }
+
+ return wrap(realm, entity);
+ }
+
+ @Override
+ public void removeClientInitialAccessModel(RealmModel realm, String id) {
+ tx.remove(getCache(false), id);
+ }
+
+ @Override
+ public List<ClientInitialAccessModel> listClientInitialAccess(RealmModel realm) {
+ Map<String, ClientInitialAccessEntity> entities = new MapReduceTask(sessionCache)
+ .mappedWith(ClientInitialAccessMapper.create(realm.getId()))
+ .reducedWith(new FirstResultReducer())
+ .execute();
+ return wrapClientInitialAccess(realm, entities.values());
+ }
+
class InfinispanKeycloakTransaction implements KeycloakTransaction {
private boolean active;
diff --git a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/mapreduce/ClientInitialAccessMapper.java b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/mapreduce/ClientInitialAccessMapper.java
new file mode 100644
index 0000000..87c9b3c
--- /dev/null
+++ b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/mapreduce/ClientInitialAccessMapper.java
@@ -0,0 +1,80 @@
+package org.keycloak.models.sessions.infinispan.mapreduce;
+
+import org.infinispan.distexec.mapreduce.Collector;
+import org.infinispan.distexec.mapreduce.Mapper;
+import org.keycloak.models.sessions.infinispan.entities.ClientInitialAccessEntity;
+import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
+
+import java.io.Serializable;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class ClientInitialAccessMapper implements Mapper<String, SessionEntity, String, Object>, Serializable {
+
+ public ClientInitialAccessMapper(String realm) {
+ this.realm = realm;
+ }
+
+ private enum EmitValue {
+ KEY, ENTITY
+ }
+
+ private String realm;
+
+ private EmitValue emit = EmitValue.ENTITY;
+
+ private Integer time;
+ private Integer remainingCount;
+
+ public static ClientInitialAccessMapper create(String realm) {
+ return new ClientInitialAccessMapper(realm);
+ }
+
+ public ClientInitialAccessMapper emitKey() {
+ emit = EmitValue.KEY;
+ return this;
+ }
+
+ public ClientInitialAccessMapper time(int time) {
+ this.time = time;
+ return this;
+ }
+
+
+ public ClientInitialAccessMapper remainingCount(int remainingCount) {
+ this.remainingCount = remainingCount;
+ return this;
+ }
+
+ @Override
+ public void map(String key, SessionEntity e, Collector collector) {
+ if (!realm.equals(e.getRealm())) {
+ return;
+ }
+
+ if (!(e instanceof ClientInitialAccessEntity)) {
+ return;
+ }
+
+ ClientInitialAccessEntity entity = (ClientInitialAccessEntity) e;
+
+ if (time != null && entity.getExpiration() > 0 && (entity.getTimestamp() + entity.getExpiration()) < time) {
+ return;
+ }
+
+ if (remainingCount != null && entity.getRemainingCount() == remainingCount) {
+ return;
+ }
+
+ switch (emit) {
+ case KEY:
+ collector.emit(key, key);
+ break;
+ case ENTITY:
+ collector.emit(key, entity);
+ break;
+ }
+ }
+
+}
diff --git a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/clientregistration/EntityDescriptorClientRegistrationProvider.java b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/clientregistration/EntityDescriptorClientRegistrationProvider.java
index 298623d..81c2df4 100644
--- a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/clientregistration/EntityDescriptorClientRegistrationProvider.java
+++ b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/clientregistration/EntityDescriptorClientRegistrationProvider.java
@@ -2,26 +2,10 @@ package org.keycloak.protocol.saml.clientregistration;
import org.jboss.logging.Logger;
import org.keycloak.events.EventBuilder;
-import org.keycloak.events.EventType;
-import org.keycloak.exportimport.ClientDescriptionConverter;
-import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
-import org.keycloak.models.ModelDuplicateException;
-import org.keycloak.models.utils.ModelToRepresentation;
-import org.keycloak.models.utils.RepresentationToModel;
-import org.keycloak.protocol.saml.EntityDescriptorDescriptionConverter;
-import org.keycloak.representations.idm.ClientRepresentation;
-import org.keycloak.services.ErrorResponse;
-import org.keycloak.services.clientregistration.ClientRegAuth;
+import org.keycloak.services.clientregistration.ClientRegistrationAuth;
import org.keycloak.services.clientregistration.ClientRegistrationProvider;
-import javax.ws.rs.Consumes;
-import javax.ws.rs.POST;
-import javax.ws.rs.Produces;
-import javax.ws.rs.core.MediaType;
-import javax.ws.rs.core.Response;
-import java.net.URI;
-
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
@@ -31,7 +15,7 @@ public class EntityDescriptorClientRegistrationProvider implements ClientRegistr
private KeycloakSession session;
private EventBuilder event;
- private ClientRegAuth auth;
+ private ClientRegistrationAuth auth;
public EntityDescriptorClientRegistrationProvider(KeycloakSession session) {
this.session = session;
@@ -67,7 +51,7 @@ public class EntityDescriptorClientRegistrationProvider implements ClientRegistr
}
@Override
- public void setAuth(ClientRegAuth auth) {
+ public void setAuth(ClientRegistrationAuth auth) {
this.auth = auth;
}
diff --git a/services/src/main/java/org/keycloak/services/clientregistration/AdapterInstallationClientRegistrationProvider.java b/services/src/main/java/org/keycloak/services/clientregistration/AdapterInstallationClientRegistrationProvider.java
index feffc5f..c667b5e 100644
--- a/services/src/main/java/org/keycloak/services/clientregistration/AdapterInstallationClientRegistrationProvider.java
+++ b/services/src/main/java/org/keycloak/services/clientregistration/AdapterInstallationClientRegistrationProvider.java
@@ -1,6 +1,5 @@
package org.keycloak.services.clientregistration;
-import org.jboss.resteasy.spi.UnauthorizedException;
import org.keycloak.authentication.AuthenticationProcessor;
import org.keycloak.events.Errors;
import org.keycloak.events.EventBuilder;
@@ -23,7 +22,7 @@ public class AdapterInstallationClientRegistrationProvider implements ClientRegi
private KeycloakSession session;
private EventBuilder event;
- private ClientRegAuth auth;
+ private ClientRegistrationAuth auth;
public AdapterInstallationClientRegistrationProvider(KeycloakSession session) {
this.session = session;
@@ -51,7 +50,7 @@ public class AdapterInstallationClientRegistrationProvider implements ClientRegi
}
@Override
- public void setAuth(ClientRegAuth auth) {
+ public void setAuth(ClientRegistrationAuth auth) {
this.auth = auth;
}
diff --git a/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationProvider.java b/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationProvider.java
index 0c6bc4e..98bd6f9 100644
--- a/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationProvider.java
+++ b/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationProvider.java
@@ -1,7 +1,6 @@
package org.keycloak.services.clientregistration;
import org.keycloak.events.EventBuilder;
-import org.keycloak.models.RealmModel;
import org.keycloak.provider.Provider;
/**
@@ -9,7 +8,7 @@ import org.keycloak.provider.Provider;
*/
public interface ClientRegistrationProvider extends Provider {
- void setAuth(ClientRegAuth auth);
+ void setAuth(ClientRegistrationAuth auth);
void setEvent(EventBuilder event);
diff --git a/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationService.java b/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationService.java
index 2aed3f1..0581388 100644
--- a/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationService.java
+++ b/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationService.java
@@ -35,7 +35,7 @@ public class ClientRegistrationService {
}
provider.setEvent(event);
- provider.setAuth(new ClientRegAuth(session, event));
+ provider.setAuth(new ClientRegistrationAuth(session, event));
return provider;
}
diff --git a/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationTokenUtils.java b/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationTokenUtils.java
new file mode 100644
index 0000000..7cd3342
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationTokenUtils.java
@@ -0,0 +1,99 @@
+package org.keycloak.services.clientregistration;
+
+import org.keycloak.common.util.Time;
+import org.keycloak.jose.jws.JWSBuilder;
+import org.keycloak.jose.jws.JWSInput;
+import org.keycloak.jose.jws.crypto.RSAProvider;
+import org.keycloak.models.ClientInitialAccessModel;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.representations.JsonWebToken;
+import org.keycloak.services.ForbiddenException;
+import org.keycloak.services.Urls;
+import org.keycloak.util.TokenUtil;
+
+import javax.ws.rs.core.UriInfo;
+import java.io.IOException;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class ClientRegistrationTokenUtils {
+
+ public static final String TYPE_INITIAL_ACCESS_TOKEN = "InitialAccessToken";
+ public static final String TYPE_REGISTRATION_ACCESS_TOKEN = "RegistrationAccessToken";
+
+ public static String updateRegistrationAccessToken(KeycloakSession session, ClientModel client) {
+ return updateRegistrationAccessToken(session.getContext().getRealm(), session.getContext().getUri(), client);
+ }
+
+ public static String updateRegistrationAccessToken(RealmModel realm, UriInfo uri, ClientModel client) {
+ String id = KeycloakModelUtils.generateId();
+ client.setRegistrationToken(id);
+ String token = createToken(realm, uri, id, TYPE_REGISTRATION_ACCESS_TOKEN, 0);
+ return token;
+ }
+
+ public static String createInitialAccessToken(RealmModel realm, UriInfo uri, ClientInitialAccessModel model) {
+ return createToken(realm, uri, model.getId(), TYPE_INITIAL_ACCESS_TOKEN, model.getTimestamp() + model.getExpiration());
+ }
+
+ public static JsonWebToken parseToken(RealmModel realm, UriInfo uri, String token) {
+ JWSInput input;
+ try {
+ input = new JWSInput(token);
+ } catch (Exception e) {
+ throw new ForbiddenException(e);
+ }
+
+ if (!RSAProvider.verify(input, realm.getPublicKey())) {
+ throw new ForbiddenException("Invalid signature");
+ }
+
+ JsonWebToken jwt;
+ try {
+ jwt = input.readJsonContent(JsonWebToken.class);
+ } catch (IOException e) {
+ throw new ForbiddenException(e);
+ }
+
+ if (!getIssuer(realm, uri).equals(jwt.getIssuer())) {
+ throw new ForbiddenException("Issuer doesn't match");
+ }
+
+ if (!jwt.isActive()) {
+ throw new ForbiddenException("Expired token");
+ }
+
+ if (!(TokenUtil.TOKEN_TYPE_BEARER.equals(jwt.getType()) ||
+ TYPE_INITIAL_ACCESS_TOKEN.equals(jwt.getType()) ||
+ TYPE_REGISTRATION_ACCESS_TOKEN.equals(jwt.getType()))) {
+ throw new ForbiddenException("Invalid token type");
+ }
+
+ return jwt;
+ }
+
+ private static String createToken(RealmModel realm, UriInfo uri, String id, String type, int expiration) {
+ JsonWebToken jwt = new JsonWebToken();
+
+ String issuer = getIssuer(realm, uri);
+
+ jwt.type(type);
+ jwt.id(id);
+ jwt.issuedAt(Time.currentTime());
+ jwt.expiration(expiration);
+ jwt.issuer(issuer);
+ jwt.audience(issuer);
+
+ String token = new JWSBuilder().jsonContent(jwt).rsa256(realm.getPrivateKey());
+ return token;
+ }
+
+ private static String getIssuer(RealmModel realm, UriInfo uri) {
+ return Urls.realmIssuer(uri.getBaseUri(), realm.getName());
+ }
+
+}
diff --git a/services/src/main/java/org/keycloak/services/clientregistration/DefaultClientRegistrationProvider.java b/services/src/main/java/org/keycloak/services/clientregistration/DefaultClientRegistrationProvider.java
index 0fad9c2..38d71f2 100644
--- a/services/src/main/java/org/keycloak/services/clientregistration/DefaultClientRegistrationProvider.java
+++ b/services/src/main/java/org/keycloak/services/clientregistration/DefaultClientRegistrationProvider.java
@@ -2,10 +2,10 @@ package org.keycloak.services.clientregistration;
import org.keycloak.events.EventBuilder;
import org.keycloak.events.EventType;
+import org.keycloak.models.ClientInitialAccessModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ModelDuplicateException;
-import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.models.utils.ModelToRepresentation;
import org.keycloak.models.utils.RepresentationToModel;
import org.keycloak.representations.idm.ClientRepresentation;
@@ -23,7 +23,7 @@ public class DefaultClientRegistrationProvider implements ClientRegistrationProv
private KeycloakSession session;
private EventBuilder event;
- private ClientRegAuth auth;
+ private ClientRegistrationAuth auth;
public DefaultClientRegistrationProvider(KeycloakSession session) {
this.session = session;
@@ -39,11 +39,19 @@ public class DefaultClientRegistrationProvider implements ClientRegistrationProv
try {
ClientModel clientModel = RepresentationToModel.createClient(session, session.getContext().getRealm(), client, true);
- KeycloakModelUtils.generateRegistrationAccessToken(clientModel);
-
client = ModelToRepresentation.toRepresentation(clientModel);
+
+ String registrationAccessToken = ClientRegistrationTokenUtils.updateRegistrationAccessToken(session, clientModel);
+
+ client.setRegistrationAccessToken(registrationAccessToken);
+
URI uri = session.getContext().getUri().getAbsolutePathBuilder().path(clientModel.getId()).build();
+ if (auth.isInitialAccessToken()) {
+ ClientInitialAccessModel initialAccessModel = auth.getInitialAccessModel();
+ initialAccessModel.decreaseRemainingCount();
+ }
+
event.client(client.getClientId()).success();
return Response.created(uri).entity(client).build();
} catch (ModelDuplicateException e) {
@@ -60,12 +68,15 @@ public class DefaultClientRegistrationProvider implements ClientRegistrationProv
ClientModel client = session.getContext().getRealm().getClientByClientId(clientId);
auth.requireView(client);
+ ClientRepresentation rep = ModelToRepresentation.toRepresentation(client);
+
if (auth.isRegistrationAccessToken()) {
- KeycloakModelUtils.generateRegistrationAccessToken(client);
+ String registrationAccessToken = ClientRegistrationTokenUtils.updateRegistrationAccessToken(session, client);
+ rep.setRegistrationAccessToken(registrationAccessToken);
}
event.client(client.getClientId()).success();
- return Response.ok(ModelToRepresentation.toRepresentation(client)).build();
+ return Response.ok(rep).build();
}
@PUT
@@ -78,13 +89,13 @@ public class DefaultClientRegistrationProvider implements ClientRegistrationProv
auth.requireUpdate(client);
RepresentationToModel.updateClient(rep, client);
+ rep = ModelToRepresentation.toRepresentation(client);
if (auth.isRegistrationAccessToken()) {
- KeycloakModelUtils.generateRegistrationAccessToken(client);
+ String registrationAccessToken = ClientRegistrationTokenUtils.updateRegistrationAccessToken(session, client);
+ rep.setRegistrationAccessToken(registrationAccessToken);
}
- rep = ModelToRepresentation.toRepresentation(client);
-
event.client(client.getClientId()).success();
return Response.ok(rep).build();
}
@@ -106,7 +117,7 @@ public class DefaultClientRegistrationProvider implements ClientRegistrationProv
}
@Override
- public void setAuth(ClientRegAuth auth) {
+ public void setAuth(ClientRegistrationAuth auth) {
this.auth = auth;
}
diff --git a/services/src/main/java/org/keycloak/services/clientregistration/oidc/OIDCClientRegistrationProvider.java b/services/src/main/java/org/keycloak/services/clientregistration/oidc/OIDCClientRegistrationProvider.java
index b27ddb0..961f28d 100644
--- a/services/src/main/java/org/keycloak/services/clientregistration/oidc/OIDCClientRegistrationProvider.java
+++ b/services/src/main/java/org/keycloak/services/clientregistration/oidc/OIDCClientRegistrationProvider.java
@@ -1,28 +1,11 @@
package org.keycloak.services.clientregistration.oidc;
import org.jboss.logging.Logger;
-import org.keycloak.common.util.Time;
import org.keycloak.events.EventBuilder;
-import org.keycloak.events.EventType;
-import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
-import org.keycloak.models.ModelDuplicateException;
-import org.keycloak.models.utils.ModelToRepresentation;
-import org.keycloak.models.utils.RepresentationToModel;
-import org.keycloak.protocol.oidc.OIDCClientDescriptionConverter;
-import org.keycloak.protocol.oidc.representations.OIDCClientRepresentation;
-import org.keycloak.representations.idm.ClientRepresentation;
-import org.keycloak.services.ErrorResponse;
-import org.keycloak.services.clientregistration.ClientRegAuth;
+import org.keycloak.services.clientregistration.ClientRegistrationAuth;
import org.keycloak.services.clientregistration.ClientRegistrationProvider;
-import javax.ws.rs.Consumes;
-import javax.ws.rs.POST;
-import javax.ws.rs.Produces;
-import javax.ws.rs.core.MediaType;
-import javax.ws.rs.core.Response;
-import java.net.URI;
-
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
@@ -32,7 +15,7 @@ public class OIDCClientRegistrationProvider implements ClientRegistrationProvide
private KeycloakSession session;
private EventBuilder event;
- private ClientRegAuth auth;
+ private ClientRegistrationAuth auth;
public OIDCClientRegistrationProvider(KeycloakSession session) {
this.session = session;
@@ -55,7 +38,7 @@ public class OIDCClientRegistrationProvider implements ClientRegistrationProvide
//
// String registrationAccessToken = TokenGenerator.createRegistrationAccessToken();
//
-// clientModel.setRegistrationSecret(registrationAccessToken);
+// clientModel.setRegistrationToken(registrationAccessToken);
//
// URI uri = session.getContext().getUri().getAbsolutePathBuilder().path(clientModel.getId()).build();
//
@@ -87,7 +70,7 @@ public class OIDCClientRegistrationProvider implements ClientRegistrationProvide
}
@Override
- public void setAuth(ClientRegAuth auth) {
+ public void setAuth(ClientRegistrationAuth auth) {
this.auth = auth;
}
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientInitialAccessResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientInitialAccessResource.java
new file mode 100644
index 0000000..7ac2036
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientInitialAccessResource.java
@@ -0,0 +1,102 @@
+package org.keycloak.services.resources.admin;
+
+import org.keycloak.events.admin.OperationType;
+import org.keycloak.models.ClientInitialAccessModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.representations.idm.ClientInitialAccessCreatePresentation;
+import org.keycloak.representations.idm.ClientInitialAccessPresentation;
+import org.keycloak.services.clientregistration.ClientRegistrationTokenUtils;
+
+import javax.servlet.http.HttpServletResponse;
+import javax.ws.rs.*;
+import javax.ws.rs.core.*;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class ClientInitialAccessResource {
+
+ private final RealmAuth auth;
+ private final RealmModel realm;
+ private final AdminEventBuilder adminEvent;
+
+ @Context
+ protected KeycloakSession session;
+
+ @Context
+ protected UriInfo uriInfo;
+
+ public ClientInitialAccessResource(RealmModel realm, RealmAuth auth, AdminEventBuilder adminEvent) {
+ this.auth = auth;
+ this.realm = realm;
+ this.adminEvent = adminEvent;
+
+ auth.init(RealmAuth.Resource.CLIENT);
+ }
+
+ /**
+ * Create a new initial access token.
+ *
+ * @param config
+ * @return
+ */
+ @POST
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Produces(MediaType.APPLICATION_JSON)
+ public ClientInitialAccessPresentation create(ClientInitialAccessCreatePresentation config, @Context final HttpServletResponse response) {
+ auth.requireManage();
+
+ int expiration = config.getExpiration() != null ? config.getExpiration() : 0;
+ int count = config.getCount() != null ? config.getCount() : 1;
+
+ ClientInitialAccessModel clientInitialAccessModel = session.sessions().createClientInitialAccessModel(realm, expiration, count);
+
+ adminEvent.operation(OperationType.CREATE).resourcePath(uriInfo, clientInitialAccessModel.getId()).representation(config).success();
+
+ if (session.getTransaction().isActive()) {
+ session.getTransaction().commit();
+ }
+
+ ClientInitialAccessPresentation rep = wrap(clientInitialAccessModel);
+
+ String token = ClientRegistrationTokenUtils.createInitialAccessToken(realm, uriInfo, clientInitialAccessModel);
+ rep.setToken(token);
+
+ response.setStatus(Response.Status.CREATED.getStatusCode());
+ response.setHeader(HttpHeaders.LOCATION, uriInfo.getAbsolutePathBuilder().path(clientInitialAccessModel.getId()).build().toString());
+
+ return rep;
+ }
+
+ @GET
+ @Produces(MediaType.APPLICATION_JSON)
+ public List<ClientInitialAccessPresentation> list() {
+ List<ClientInitialAccessModel> models = session.sessions().listClientInitialAccess(realm);
+ List<ClientInitialAccessPresentation> reps = new LinkedList<>();
+ for (ClientInitialAccessModel m : models) {
+ ClientInitialAccessPresentation r = wrap(m);
+ reps.add(r);
+ }
+ return reps;
+ }
+
+ @DELETE
+ @Path("{id}")
+ public void delete(final @PathParam("id") String id) {
+ session.sessions().removeClientInitialAccessModel(realm, id);
+ }
+
+ private ClientInitialAccessPresentation wrap(ClientInitialAccessModel model) {
+ ClientInitialAccessPresentation rep = new ClientInitialAccessPresentation();
+ rep.setId(model.getId());
+ rep.setTimestamp(model.getTimestamp());
+ rep.setExpiration(model.getExpiration());
+ rep.setCount(model.getCount());
+ rep.setRemainingCount(model.getRemainingCount());
+ return rep;
+ }
+
+}
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java
index f729c7f..03b0636 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java
@@ -22,6 +22,7 @@ import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.representations.idm.UserSessionRepresentation;
+import org.keycloak.services.clientregistration.ClientRegistrationTokenUtils;
import org.keycloak.services.managers.ClientManager;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.services.managers.ResourceAdminManager;
@@ -228,8 +229,11 @@ public class ClientResource {
public ClientRepresentation regenerateRegistrationAccessToken() {
auth.requireManage();
- KeycloakModelUtils.generateRegistrationAccessToken(client);
+ String token = ClientRegistrationTokenUtils.updateRegistrationAccessToken(realm, uriInfo, client);
+
ClientRepresentation rep = ModelToRepresentation.toRepresentation(client);
+ rep.setRegistrationAccessToken(token);
+
adminEvent.operation(OperationType.ACTION).resourcePath(uriInfo).representation(rep).success();
return rep;
}
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java
index 6ef9e1e..36af035 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java
@@ -141,6 +141,18 @@ public class RealmAdminResource {
}
/**
+ * Base path for managing client initial access tokens
+ *
+ * @return
+ */
+ @Path("clients-initial-access")
+ public ClientInitialAccessResource getClientInitialAccess() {
+ ClientInitialAccessResource resource = new ClientInitialAccessResource(realm, auth, adminEvent);
+ ResteasyProviderFactory.getInstance().injectProperties(resource);
+ return resource;
+ }
+
+ /**
* base path for managing realm-level roles of this realm
*
* @return
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/ClientInitialAccessTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/ClientInitialAccessTest.java
new file mode 100644
index 0000000..79ef1ac
--- /dev/null
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/ClientInitialAccessTest.java
@@ -0,0 +1,58 @@
+package org.keycloak.testsuite.admin;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.keycloak.admin.client.resource.ClientInitialAccessResource;
+import org.keycloak.representations.idm.ClientInitialAccessCreatePresentation;
+import org.keycloak.representations.idm.ClientInitialAccessPresentation;
+
+import javax.ws.rs.core.Response;
+import java.net.URI;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class ClientInitialAccessTest extends AbstractClientTest {
+
+ @Test
+ public void create() {
+ ClientInitialAccessResource resource = keycloak.realm(REALM_NAME).clientInitialAccess();
+
+ ClientInitialAccessPresentation access = resource.create(new ClientInitialAccessCreatePresentation(1000, 2));
+ Assert.assertEquals(new Integer(2), access.getCount());
+ Assert.assertEquals(new Integer(2), access.getRemainingCount());
+ Assert.assertEquals(new Integer(1000), access.getExpiration());
+ Assert.assertNotNull(access.getTimestamp());
+ Assert.assertNotNull(access.getToken());
+
+ ClientInitialAccessPresentation access2 = resource.create(new ClientInitialAccessCreatePresentation());
+
+ List<ClientInitialAccessPresentation> list = resource.list();
+ Assert.assertEquals(2, list.size());
+
+ for (ClientInitialAccessPresentation r : list) {
+ if (r.getId().equals(access.getId())) {
+ Assert.assertEquals(new Integer(2), r.getCount());
+ Assert.assertEquals(new Integer(2), r.getRemainingCount());
+ Assert.assertEquals(new Integer(1000), r.getExpiration());
+ Assert.assertNotNull(r.getTimestamp());
+ Assert.assertNull(r.getToken());
+ } else if(r.getId().equals(access2.getId())) {
+ Assert.assertEquals(new Integer(1), r.getCount());
+ Assert.assertEquals(new Integer(1), r.getRemainingCount());
+ Assert.assertEquals(new Integer(0), r.getExpiration());
+ Assert.assertNotNull(r.getTimestamp());
+ Assert.assertNull(r.getToken());
+ } else {
+ Assert.fail("Unexpected id");
+ }
+ }
+
+ resource.delete(access.getId());
+ resource.delete(access2.getId());
+
+ Assert.assertTrue(resource.list().isEmpty());
+ }
+
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/AbstractClientRegistrationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/AbstractClientRegistrationTest.java
index 8b8dfad..8535d46 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/AbstractClientRegistrationTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/AbstractClientRegistrationTest.java
@@ -2,6 +2,7 @@ package org.keycloak.testsuite.client;
import org.junit.After;
import org.junit.Before;
+import org.keycloak.client.registration.Auth;
import org.keycloak.client.registration.ClientRegistration;
import org.keycloak.client.registration.ClientRegistrationException;
import org.keycloak.models.AdminRoles;
@@ -13,7 +14,6 @@ import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.testsuite.AbstractKeycloakTest;
import javax.ws.rs.NotFoundException;
-import javax.ws.rs.core.Response;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
@@ -76,13 +76,11 @@ public abstract class AbstractClientRegistrationTest extends AbstractKeycloakTes
testRealms.add(rep);
}
- public ClientRepresentation createClient(ClientRepresentation client) {
- Response response = adminClient.realm(REALM_NAME).clients().create(client);
- String id = response.getLocation().toString();
- id = id.substring(id.lastIndexOf('/') + 1);
- client.setId(id);
- response.close();
- return client;
+ public ClientRepresentation createClient(ClientRepresentation client) throws ClientRegistrationException {
+ authManageClients();
+ ClientRepresentation response = reg.create(client);
+ reg.auth(null);
+ return response;
}
public ClientRepresentation getClient(String clientId) {
@@ -93,4 +91,20 @@ public abstract class AbstractClientRegistrationTest extends AbstractKeycloakTes
}
}
+ void authCreateClients() {
+ reg.auth(Auth.token(getToken("create-clients", "password")));
+ }
+
+ void authManageClients() {
+ reg.auth(Auth.token(getToken("manage-clients", "password")));
+ }
+
+ void authNoAccess() {
+ reg.auth(Auth.token(getToken("no-access", "password")));
+ }
+
+ private String getToken(String username, String password) {
+ return oauthClient.getToken(REALM_NAME, "security-admin-console", null, username, password).getToken();
+ }
+
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/ClientRegistrationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/ClientRegistrationTest.java
index 8b84b10..3390988 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/ClientRegistrationTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/ClientRegistrationTest.java
@@ -196,20 +196,4 @@ public class ClientRegistrationTest extends AbstractClientRegistrationTest {
}
}
- private void authCreateClients() {
- reg.auth(Auth.token(getToken("create-clients", "password")));
- }
-
- private void authManageClients() {
- reg.auth(Auth.token(getToken("manage-clients", "password")));
- }
-
- private void authNoAccess() {
- reg.auth(Auth.token(getToken("no-access", "password")));
- }
-
- private String getToken(String username, String password) {
- return oauthClient.getToken(REALM_NAME, "security-admin-console", null, username, password).getToken();
- }
-
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/InitialAccessTokenTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/InitialAccessTokenTest.java
new file mode 100644
index 0000000..0ef75dd
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/InitialAccessTokenTest.java
@@ -0,0 +1,101 @@
+package org.keycloak.testsuite.client;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.keycloak.admin.client.resource.ClientInitialAccessResource;
+import org.keycloak.client.registration.Auth;
+import org.keycloak.client.registration.ClientRegistrationException;
+import org.keycloak.client.registration.HttpErrorException;
+import org.keycloak.representations.idm.ClientInitialAccessCreatePresentation;
+import org.keycloak.representations.idm.ClientInitialAccessPresentation;
+import org.keycloak.representations.idm.ClientRepresentation;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class InitialAccessTokenTest extends AbstractClientRegistrationTest {
+
+ private ClientInitialAccessResource resource;
+
+ @Before
+ public void before() throws Exception {
+ super.before();
+
+ resource = adminClient.realm(REALM_NAME).clientInitialAccess();
+ }
+
+ @Test
+ public void create() throws ClientRegistrationException {
+ ClientInitialAccessPresentation response = resource.create(new ClientInitialAccessCreatePresentation());
+
+ reg.auth(Auth.token(response));
+
+ ClientRepresentation rep = new ClientRepresentation();
+
+ ClientRepresentation created = reg.create(rep);
+ Assert.assertNotNull(created);
+
+ try {
+ reg.create(rep);
+ } catch (ClientRegistrationException e) {
+ Assert.assertEquals(403, ((HttpErrorException) e.getCause()).getStatusLine().getStatusCode());
+ }
+ }
+
+ @Test
+ public void createMultiple() throws ClientRegistrationException {
+ ClientInitialAccessPresentation response = resource.create(new ClientInitialAccessCreatePresentation(0, 2));
+
+ reg.auth(Auth.token(response));
+
+ ClientRepresentation rep = new ClientRepresentation();
+
+ ClientRepresentation created = reg.create(rep);
+ Assert.assertNotNull(created);
+
+ created = reg.create(rep);
+ Assert.assertNotNull(created);
+
+ try {
+ reg.create(rep);
+ } catch (ClientRegistrationException e) {
+ Assert.assertEquals(403, ((HttpErrorException) e.getCause()).getStatusLine().getStatusCode());
+ }
+ }
+
+ @Test
+ public void createExpired() throws ClientRegistrationException, InterruptedException {
+ ClientInitialAccessPresentation response = resource.create(new ClientInitialAccessCreatePresentation(1, 1));
+
+ reg.auth(Auth.token(response));
+
+ ClientRepresentation rep = new ClientRepresentation();
+
+ Thread.sleep(2);
+
+ try {
+ reg.create(rep);
+ } catch (ClientRegistrationException e) {
+ Assert.assertEquals(403, ((HttpErrorException) e.getCause()).getStatusLine().getStatusCode());
+ }
+ }
+
+ @Test
+ public void createDeleted() throws ClientRegistrationException, InterruptedException {
+ ClientInitialAccessPresentation response = resource.create(new ClientInitialAccessCreatePresentation());
+
+ reg.auth(Auth.token(response));
+
+ resource.delete(response.getId());
+
+ ClientRepresentation rep = new ClientRepresentation();
+
+ try {
+ reg.create(rep);
+ } catch (ClientRegistrationException e) {
+ Assert.assertEquals(403, ((HttpErrorException) e.getCause()).getStatusLine().getStatusCode());
+ }
+ }
+
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/RegistrationAccessTokenTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/RegistrationAccessTokenTest.java
index be880bf..cad28ab 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/RegistrationAccessTokenTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/RegistrationAccessTokenTest.java
@@ -7,8 +7,6 @@ import org.keycloak.client.registration.ClientRegistrationException;
import org.keycloak.client.registration.HttpErrorException;
import org.keycloak.representations.idm.ClientRepresentation;
-import javax.ws.rs.core.Response;
-
import static org.junit.Assert.*;
/**
@@ -22,13 +20,13 @@ public class RegistrationAccessTokenTest extends AbstractClientRegistrationTest
public void before() throws Exception {
super.before();
- client = new ClientRepresentation();
- client.setEnabled(true);
- client.setClientId("RegistrationAccessTokenTest");
- client.setSecret("RegistrationAccessTokenTestClientSecret");
- client.setRegistrationAccessToken("RegistrationAccessTokenTestRegistrationAccessToken");
- client.setRootUrl("http://root");
- client = createClient(client);
+ ClientRepresentation c = new ClientRepresentation();
+ c.setEnabled(true);
+ c.setClientId("RegistrationAccessTokenTest");
+ c.setSecret("RegistrationAccessTokenTestClientSecret");
+ c.setRootUrl("http://root");
+
+ client = createClient(c);
reg.auth(Auth.token(client.getRegistrationAccessToken()));
}
@@ -36,7 +34,7 @@ public class RegistrationAccessTokenTest extends AbstractClientRegistrationTest
private ClientRepresentation assertRead(String id, String registrationAccess, boolean expectSuccess) throws ClientRegistrationException {
if (expectSuccess) {
reg.auth(Auth.token(registrationAccess));
- ClientRepresentation rep = reg.get(client.getClientId());
+ ClientRepresentation rep = reg.get(id);
assertNotNull(rep);
return rep;
} else {
@@ -76,6 +74,7 @@ public class RegistrationAccessTokenTest extends AbstractClientRegistrationTest
@Test
public void updateClientWithRegistrationToken() throws ClientRegistrationException {
client.setRootUrl("http://newroot");
+
ClientRepresentation rep = reg.update(client);
assertEquals("http://newroot", getClient(client.getId()).getRootUrl());