Merge pull request #3370 from mposolda/master
Client registration policies - polishing
This commit is contained in:
commit
2acea2b2ee
39 changed files with 515 additions and 744 deletions
|
@ -1,60 +0,0 @@
|
|||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.representations.idm;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class ClientRegistrationTrustedHostRepresentation {
|
||||
|
||||
String hostName;
|
||||
Integer count;
|
||||
Integer remainingCount;
|
||||
|
||||
public static ClientRegistrationTrustedHostRepresentation create(String hostName, int count, int remainingCount) {
|
||||
ClientRegistrationTrustedHostRepresentation rep = new ClientRegistrationTrustedHostRepresentation();
|
||||
rep.setHostName(hostName);
|
||||
rep.setCount(count);
|
||||
rep.setRemainingCount(remainingCount);
|
||||
return rep;
|
||||
}
|
||||
|
||||
public String getHostName() {
|
||||
return hostName;
|
||||
}
|
||||
|
||||
public void setHostName(String hostName) {
|
||||
this.hostName = hostName;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
|
@ -1,63 +0,0 @@
|
|||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.admin.client.resource;
|
||||
|
||||
import org.keycloak.representations.idm.ClientRegistrationTrustedHostRepresentation;
|
||||
|
||||
import javax.ws.rs.Consumes;
|
||||
import javax.ws.rs.DELETE;
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.POST;
|
||||
import javax.ws.rs.PUT;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.PathParam;
|
||||
import javax.ws.rs.Produces;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.Response;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public interface ClientRegistrationTrustedHostResource {
|
||||
|
||||
@POST
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
Response create(ClientRegistrationTrustedHostRepresentation config);
|
||||
|
||||
@PUT
|
||||
@Path("{hostname}")
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
Response update(final @PathParam("hostname") String hostName, ClientRegistrationTrustedHostRepresentation config);
|
||||
|
||||
@GET
|
||||
@Path("{hostname}")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
ClientRegistrationTrustedHostRepresentation get(final @PathParam("hostname") String hostName);
|
||||
|
||||
@GET
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
List<ClientRegistrationTrustedHostRepresentation> list();
|
||||
|
||||
@DELETE
|
||||
@Path("{hostname}")
|
||||
void delete(final @PathParam("hostname") String hostName);
|
||||
|
||||
}
|
|
@ -1,87 +0,0 @@
|
|||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.models.sessions.infinispan;
|
||||
|
||||
import org.infinispan.Cache;
|
||||
import org.keycloak.models.ClientRegistrationTrustedHostModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.sessions.infinispan.entities.ClientRegistrationTrustedHostEntity;
|
||||
import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class ClientRegistrationTrustedHostAdapter implements ClientRegistrationTrustedHostModel {
|
||||
|
||||
private final KeycloakSession session;
|
||||
private final InfinispanUserSessionProvider provider;
|
||||
private final Cache<String, SessionEntity> cache;
|
||||
private final RealmModel realm;
|
||||
private final ClientRegistrationTrustedHostEntity entity;
|
||||
|
||||
public ClientRegistrationTrustedHostAdapter(KeycloakSession session, InfinispanUserSessionProvider provider, Cache<String, SessionEntity> cache, RealmModel realm, ClientRegistrationTrustedHostEntity entity) {
|
||||
this.session = session;
|
||||
this.provider = provider;
|
||||
this.cache = cache;
|
||||
this.realm = realm;
|
||||
this.entity = entity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RealmModel getRealm() {
|
||||
return realm;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHostName() {
|
||||
return entity.getHostName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return entity.getCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCount(int count) {
|
||||
entity.setCount(count);
|
||||
update();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRemainingCount() {
|
||||
return entity.getRemainingCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRemainingCount(int remainingCount) {
|
||||
entity.setRemainingCount(remainingCount);
|
||||
update();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decreaseRemainingCount() {
|
||||
entity.setRemainingCount(entity.getRemainingCount() - 1);
|
||||
update();
|
||||
}
|
||||
|
||||
void update() {
|
||||
provider.getTx().replace(cache, entity.getId(), entity);
|
||||
}
|
||||
}
|
|
@ -23,11 +23,9 @@ import org.jboss.logging.Logger;
|
|||
import org.keycloak.common.util.Time;
|
||||
import org.keycloak.models.ClientInitialAccessModel;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.ClientRegistrationTrustedHostModel;
|
||||
import org.keycloak.models.ClientSessionModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakTransaction;
|
||||
import org.keycloak.models.ModelDuplicateException;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserLoginFailureModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
|
@ -35,14 +33,12 @@ import org.keycloak.models.UserSessionModel;
|
|||
import org.keycloak.models.UserSessionProvider;
|
||||
import org.keycloak.models.session.UserSessionPersisterProvider;
|
||||
import org.keycloak.models.sessions.infinispan.entities.ClientInitialAccessEntity;
|
||||
import org.keycloak.models.sessions.infinispan.entities.ClientRegistrationTrustedHostEntity;
|
||||
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.stream.ClientInitialAccessPredicate;
|
||||
import org.keycloak.models.sessions.infinispan.stream.ClientRegistrationTrustedHostPredicate;
|
||||
import org.keycloak.models.sessions.infinispan.stream.ClientSessionPredicate;
|
||||
import org.keycloak.models.sessions.infinispan.stream.Comparators;
|
||||
import org.keycloak.models.sessions.infinispan.stream.Mappers;
|
||||
|
@ -540,12 +536,6 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
|
|||
return entity != null ? new ClientInitialAccessAdapter(session, this, cache, realm, entity) : null;
|
||||
}
|
||||
|
||||
ClientRegistrationTrustedHostAdapter wrap(RealmModel realm, ClientRegistrationTrustedHostEntity entity) {
|
||||
Cache<String, SessionEntity> cache = getCache(false);
|
||||
return entity != null ? new ClientRegistrationTrustedHostAdapter(session, this, cache, realm, entity) : null;
|
||||
}
|
||||
|
||||
|
||||
UserLoginFailureModel wrap(LoginFailureKey key, LoginFailureEntity entity) {
|
||||
return entity != null ? new UserLoginFailureAdapter(this, loginFailureCache, key, entity) : null;
|
||||
}
|
||||
|
@ -737,62 +727,6 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
|
|||
return list;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientRegistrationTrustedHostModel createClientRegistrationTrustedHostModel(RealmModel realm, String hostName, int count) {
|
||||
if (getClientRegistrationTrustedHostModel(realm, hostName) != null) {
|
||||
throw new ModelDuplicateException("Client registration already exists for this realm and hostName");
|
||||
}
|
||||
|
||||
String id = computeClientRegistrationTrustedHostEntityId(realm, hostName);
|
||||
|
||||
ClientRegistrationTrustedHostEntity entity = new ClientRegistrationTrustedHostEntity();
|
||||
entity.setId(id);
|
||||
entity.setHostName(hostName);
|
||||
entity.setRealm(realm.getId());
|
||||
entity.setCount(count);
|
||||
entity.setRemainingCount(count);
|
||||
|
||||
tx.put(sessionCache, id, entity);
|
||||
|
||||
return wrap(realm, entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientRegistrationTrustedHostModel getClientRegistrationTrustedHostModel(RealmModel realm, String hostName) {
|
||||
String id = computeClientRegistrationTrustedHostEntityId(realm, hostName);
|
||||
|
||||
Cache<String, SessionEntity> cache = getCache(false);
|
||||
ClientRegistrationTrustedHostEntity entity = (ClientRegistrationTrustedHostEntity) cache.get(id);
|
||||
|
||||
// If created in this transaction
|
||||
if (entity == null) {
|
||||
entity = (ClientRegistrationTrustedHostEntity) tx.get(cache, id);
|
||||
}
|
||||
|
||||
return wrap(realm, entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeClientRegistrationTrustedHostModel(RealmModel realm, String hostName) {
|
||||
String id = computeClientRegistrationTrustedHostEntityId(realm, hostName);
|
||||
tx.remove(getCache(false), id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ClientRegistrationTrustedHostModel> listClientRegistrationTrustedHosts(RealmModel realm) {
|
||||
Iterator<Map.Entry<String, SessionEntity>> itr = sessionCache.entrySet().stream().filter(ClientRegistrationTrustedHostPredicate.create(realm.getId())).iterator();
|
||||
List<ClientRegistrationTrustedHostModel> list = new LinkedList<>();
|
||||
while (itr.hasNext()) {
|
||||
list.add(wrap(realm, (ClientRegistrationTrustedHostEntity) itr.next().getValue()));
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
private static final String CLIENT_REG_TRUSTED_HOST_ID_PREFIX = "reg:::";
|
||||
|
||||
private String computeClientRegistrationTrustedHostEntityId(RealmModel realm, String hostName) {
|
||||
return CLIENT_REG_TRUSTED_HOST_ID_PREFIX + realm.getId() + ":::" + hostName;
|
||||
}
|
||||
|
||||
class InfinispanKeycloakTransaction implements KeycloakTransaction {
|
||||
|
||||
|
|
|
@ -1,54 +0,0 @@
|
|||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.models.sessions.infinispan.entities;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class ClientRegistrationTrustedHostEntity extends SessionEntity {
|
||||
|
||||
private String hostName;
|
||||
|
||||
private int count;
|
||||
|
||||
private int remainingCount;
|
||||
|
||||
public String getHostName() {
|
||||
return hostName;
|
||||
}
|
||||
|
||||
public void setHostName(String hostName) {
|
||||
this.hostName = hostName;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
|
@ -1,58 +0,0 @@
|
|||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.models.sessions.infinispan.stream;
|
||||
|
||||
import org.keycloak.models.sessions.infinispan.entities.ClientRegistrationTrustedHostEntity;
|
||||
import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Map;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class ClientRegistrationTrustedHostPredicate implements Predicate<Map.Entry<String, SessionEntity>>, Serializable {
|
||||
|
||||
public static ClientRegistrationTrustedHostPredicate create(String realm) {
|
||||
return new ClientRegistrationTrustedHostPredicate(realm);
|
||||
}
|
||||
|
||||
private ClientRegistrationTrustedHostPredicate(String realm) {
|
||||
this.realm = realm;
|
||||
}
|
||||
|
||||
private String realm;
|
||||
|
||||
|
||||
@Override
|
||||
public boolean test(Map.Entry<String, SessionEntity> entry) {
|
||||
SessionEntity e = entry.getValue();
|
||||
|
||||
if (!realm.equals(e.getRealm())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!(e instanceof ClientRegistrationTrustedHostEntity)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
|
@ -21,11 +21,15 @@ package org.keycloak.component;
|
|||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class ComponentValidationException extends RuntimeException {
|
||||
|
||||
private Object[] parameters;
|
||||
|
||||
public ComponentValidationException() {
|
||||
}
|
||||
|
||||
public ComponentValidationException(String message) {
|
||||
public ComponentValidationException(String message, Object... parameters) {
|
||||
super(message);
|
||||
this.parameters = parameters;
|
||||
}
|
||||
|
||||
public ComponentValidationException(String message, Throwable cause) {
|
||||
|
@ -39,4 +43,12 @@ public class ComponentValidationException extends RuntimeException {
|
|||
public ComponentValidationException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
|
||||
super(message, cause, enableSuppression, writableStackTrace);
|
||||
}
|
||||
|
||||
public Object[] getParameters() {
|
||||
return parameters;
|
||||
}
|
||||
|
||||
public void setParameters(Object[] parameters) {
|
||||
this.parameters = parameters;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -61,4 +61,6 @@ public interface Details {
|
|||
String SIGNATURE_REQUIRED = "signature_required";
|
||||
String SIGNATURE_ALGORITHM = "signature_algorithm";
|
||||
|
||||
String CLIENT_REGISTRATION_POLICY = "client_registration_policy";
|
||||
|
||||
}
|
||||
|
|
|
@ -128,11 +128,6 @@ public enum ResourceType {
|
|||
*/
|
||||
, CLIENT_INITIAL_ACCESS_MODEL
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
, CLIENT_REGISTRATION_TRUSTED_HOST_MODEL
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
|
|
|
@ -1,36 +0,0 @@
|
|||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.models;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public interface ClientRegistrationTrustedHostModel {
|
||||
|
||||
RealmModel getRealm();
|
||||
|
||||
String getHostName();
|
||||
|
||||
int getCount();
|
||||
void setCount(int count);
|
||||
|
||||
int getRemainingCount();
|
||||
void setRemainingCount(int remainingCount);
|
||||
void decreaseRemainingCount();
|
||||
|
||||
}
|
|
@ -57,7 +57,7 @@ public interface UserModel extends RoleMapperModel {
|
|||
void setEnabled(boolean enabled);
|
||||
|
||||
/**
|
||||
* Set single value of specified attribute. Remove all other existing values
|
||||
* Set single value of specified attribute. Remove all other existing values of this attribute
|
||||
*
|
||||
* @param name
|
||||
* @param value
|
||||
|
|
|
@ -82,11 +82,6 @@ public interface UserSessionProvider extends Provider {
|
|||
void removeClientInitialAccessModel(RealmModel realm, String id);
|
||||
List<ClientInitialAccessModel> listClientInitialAccess(RealmModel realm);
|
||||
|
||||
ClientRegistrationTrustedHostModel createClientRegistrationTrustedHostModel(RealmModel realm, String hostName, int count);
|
||||
ClientRegistrationTrustedHostModel getClientRegistrationTrustedHostModel(RealmModel realm, String hostName);
|
||||
void removeClientRegistrationTrustedHostModel(RealmModel realm, String hostName);
|
||||
List<ClientRegistrationTrustedHostModel> listClientRegistrationTrustedHosts(RealmModel realm);
|
||||
|
||||
void close();
|
||||
|
||||
}
|
||||
|
|
|
@ -49,7 +49,7 @@ public class ConfigurationValidationHelper {
|
|||
try {
|
||||
Integer.parseInt(val);
|
||||
} catch (NumberFormatException e) {
|
||||
throw new ComponentValidationException(label + " should be a number");
|
||||
throw new ComponentValidationException("''{0}'' should be a number", label);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -68,7 +68,7 @@ public class ConfigurationValidationHelper {
|
|||
try {
|
||||
Long.parseLong(val);
|
||||
} catch (NumberFormatException e) {
|
||||
throw new ComponentValidationException(label + " should be a number");
|
||||
throw new ComponentValidationException("''{0}'' should be a number", label);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -81,7 +81,7 @@ public class ConfigurationValidationHelper {
|
|||
|
||||
public ConfigurationValidationHelper checkSingle(String key, String label, boolean required) throws ComponentValidationException {
|
||||
if (model.getConfig().containsKey(key) && model.getConfig().get(key).size() > 1) {
|
||||
throw new ComponentValidationException(label + " should be a single entry");
|
||||
throw new ComponentValidationException("''{0}'' should be a single entry", label);
|
||||
}
|
||||
|
||||
if (required) {
|
||||
|
@ -98,7 +98,7 @@ public class ConfigurationValidationHelper {
|
|||
public ConfigurationValidationHelper checkRequired(String key, String label) throws ComponentValidationException {
|
||||
List<String> values = model.getConfig().get(key);
|
||||
if (values == null) {
|
||||
throw new ComponentValidationException(label + " is required");
|
||||
throw new ComponentValidationException("''{0}'' is required", label);
|
||||
}
|
||||
|
||||
return this;
|
||||
|
@ -113,7 +113,7 @@ public class ConfigurationValidationHelper {
|
|||
|
||||
String val = model.getConfig().getFirst(key);
|
||||
if (val != null && !(val.equals("true") || val.equals("false"))) {
|
||||
throw new ComponentValidationException(label + " should be 'true' or 'false'");
|
||||
throw new ComponentValidationException("''{0}'' should be ''true'' or ''false''", label);
|
||||
}
|
||||
|
||||
return this;
|
||||
|
|
|
@ -434,8 +434,21 @@ public interface ServicesLogger extends BasicLogger {
|
|||
@Message(id=97, value="Invalid request")
|
||||
void invalidRequest(@Cause Throwable t);
|
||||
|
||||
@LogMessage(level = ERROR)
|
||||
@Message(id=98, value="Failed to get redirect uris from sector identifier URI: %s")
|
||||
void failedToGetRedirectUrisFromSectorIdentifierUri(@Cause Throwable t, String sectorIdentifierUri);
|
||||
|
||||
@LogMessage(level = WARN)
|
||||
@Message(id=99, value="Operation '%s' rejected. %s")
|
||||
void clientRegistrationRequestRejected(String opDescription, String detailedMessage);
|
||||
|
||||
@LogMessage(level = WARN)
|
||||
@Message(id=100, value= "ProtocolMapper '%s' of type '%s' not allowed")
|
||||
void clientRegistrationMapperNotAllowed(String mapperName, String mapperType);
|
||||
|
||||
@LogMessage(level = WARN)
|
||||
@Message(id=101, value= "Failed to verify remote host : %s")
|
||||
void failedToVerifyRemoteHost(String hostname);
|
||||
|
||||
@LogMessage(level = WARN)
|
||||
@Message(id=102, value= "URL '%s' doesn't match any trustedHost or trustedDomain")
|
||||
void urlDoesntMatch(String url);
|
||||
|
||||
}
|
||||
|
|
|
@ -170,6 +170,11 @@ public abstract class AbstractClientRegistrationProvider implements ClientRegist
|
|||
this.event = event;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EventBuilder getEvent() {
|
||||
return event;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
|
|
|
@ -75,6 +75,11 @@ public class AdapterInstallationClientRegistrationProvider implements ClientRegi
|
|||
this.event = event;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EventBuilder getEvent() {
|
||||
return event;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
|
|
|
@ -1,67 +0,0 @@
|
|||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.services.clientregistration;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.models.ClientRegistrationTrustedHostModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class ClientRegistrationHostUtils {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(ClientRegistrationHostUtils.class);
|
||||
|
||||
/**
|
||||
* @return null if host from request is not trusted. Otherwise return trusted host model
|
||||
*
|
||||
* TODO: Remove
|
||||
*/
|
||||
public static ClientRegistrationTrustedHostModel getTrustedHost(String hostAddress, KeycloakSession session, RealmModel realm) {
|
||||
logger.debugf("Verifying remote host : %s", hostAddress);
|
||||
|
||||
List<ClientRegistrationTrustedHostModel> trustedHosts = session.sessions().listClientRegistrationTrustedHosts(realm);
|
||||
|
||||
for (ClientRegistrationTrustedHostModel realmTrustedHost : trustedHosts) {
|
||||
try {
|
||||
if (realmTrustedHost.getRemainingCount() <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String realmHostIPAddress = InetAddress.getByName(realmTrustedHost.getHostName()).getHostAddress();
|
||||
logger.debugf("Trying host '%s' of address '%s'", realmTrustedHost.getHostName(), realmHostIPAddress);
|
||||
if (realmHostIPAddress.equals(hostAddress)) {
|
||||
logger.debugf("Successfully verified host : %s", realmTrustedHost.getHostName());
|
||||
return realmTrustedHost;
|
||||
}
|
||||
} catch (UnknownHostException uhe) {
|
||||
logger.debugf("Unknown host from realm configuration: %s", realmTrustedHost.getHostName());
|
||||
}
|
||||
}
|
||||
|
||||
logger.debugf("Failed to verify remote host : %s", hostAddress);
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
|
@ -31,4 +31,6 @@ public interface ClientRegistrationProvider extends Provider {
|
|||
|
||||
void setEvent(EventBuilder event);
|
||||
|
||||
EventBuilder getEvent();
|
||||
|
||||
}
|
||||
|
|
|
@ -18,13 +18,16 @@
|
|||
package org.keycloak.services.clientregistration.policy;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.component.ComponentModel;
|
||||
import org.keycloak.events.Details;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.services.ServicesLogger;
|
||||
import org.keycloak.services.clientregistration.ClientRegistrationContext;
|
||||
import org.keycloak.services.clientregistration.ClientRegistrationProvider;
|
||||
|
||||
|
@ -36,7 +39,7 @@ public class ClientRegistrationPolicyManager {
|
|||
private static final Logger logger = Logger.getLogger(ClientRegistrationPolicyManager.class);
|
||||
|
||||
public static void triggerBeforeRegister(ClientRegistrationContext context, RegistrationAuth authType) throws ClientRegistrationPolicyException {
|
||||
triggerPolicies(context.getSession(), authType, "before register client", (ClientRegistrationPolicy policy) -> {
|
||||
triggerPolicies(context.getSession(), context.getProvider(), authType, "before register client", (ClientRegistrationPolicy policy) -> {
|
||||
|
||||
policy.beforeRegister(context);
|
||||
|
||||
|
@ -46,7 +49,7 @@ public class ClientRegistrationPolicyManager {
|
|||
|
||||
public static void triggerAfterRegister(ClientRegistrationContext context, RegistrationAuth authType, ClientModel client) {
|
||||
try {
|
||||
triggerPolicies(context.getSession(), authType, "after register client " + client.getClientId(), (ClientRegistrationPolicy policy) -> {
|
||||
triggerPolicies(context.getSession(), context.getProvider(), authType, "after register client " + client.getClientId(), (ClientRegistrationPolicy policy) -> {
|
||||
|
||||
policy.afterRegister(context, client);
|
||||
|
||||
|
@ -58,7 +61,7 @@ public class ClientRegistrationPolicyManager {
|
|||
|
||||
|
||||
public static void triggerBeforeUpdate(ClientRegistrationContext context, RegistrationAuth authType, ClientModel client) throws ClientRegistrationPolicyException {
|
||||
triggerPolicies(context.getSession(), authType, "before update client " + client.getClientId(), (ClientRegistrationPolicy policy) -> {
|
||||
triggerPolicies(context.getSession(), context.getProvider(), authType, "before update client " + client.getClientId(), (ClientRegistrationPolicy policy) -> {
|
||||
|
||||
policy.beforeUpdate(context, client);
|
||||
|
||||
|
@ -67,7 +70,7 @@ public class ClientRegistrationPolicyManager {
|
|||
|
||||
public static void triggerAfterUpdate(ClientRegistrationContext context, RegistrationAuth authType, ClientModel client) {
|
||||
try {
|
||||
triggerPolicies(context.getSession(), authType, "after update client " + client.getClientId(), (ClientRegistrationPolicy policy) -> {
|
||||
triggerPolicies(context.getSession(), context.getProvider(), authType, "after update client " + client.getClientId(), (ClientRegistrationPolicy policy) -> {
|
||||
|
||||
policy.afterUpdate(context, client);
|
||||
|
||||
|
@ -78,7 +81,7 @@ public class ClientRegistrationPolicyManager {
|
|||
}
|
||||
|
||||
public static void triggerBeforeView(KeycloakSession session, ClientRegistrationProvider provider, RegistrationAuth authType, ClientModel client) throws ClientRegistrationPolicyException {
|
||||
triggerPolicies(session, authType, "before view client " + client.getClientId(), (ClientRegistrationPolicy policy) -> {
|
||||
triggerPolicies(session, provider, authType, "before view client " + client.getClientId(), (ClientRegistrationPolicy policy) -> {
|
||||
|
||||
policy.beforeView(provider, client);
|
||||
|
||||
|
@ -86,7 +89,7 @@ public class ClientRegistrationPolicyManager {
|
|||
}
|
||||
|
||||
public static void triggerBeforeRemove(KeycloakSession session, ClientRegistrationProvider provider, RegistrationAuth authType, ClientModel client) throws ClientRegistrationPolicyException {
|
||||
triggerPolicies(session, authType, "before delete client " + client.getClientId(), (ClientRegistrationPolicy policy) -> {
|
||||
triggerPolicies(session, provider, authType, "before delete client " + client.getClientId(), (ClientRegistrationPolicy policy) -> {
|
||||
|
||||
policy.beforeDelete(provider, client);
|
||||
|
||||
|
@ -95,7 +98,7 @@ public class ClientRegistrationPolicyManager {
|
|||
|
||||
|
||||
|
||||
private static void triggerPolicies(KeycloakSession session, RegistrationAuth authType, String opDescription, ClientRegOperation op) throws ClientRegistrationPolicyException {
|
||||
private static void triggerPolicies(KeycloakSession session, ClientRegistrationProvider provider, RegistrationAuth authType, String opDescription, ClientRegOperation op) throws ClientRegistrationPolicyException {
|
||||
RealmModel realm = session.getContext().getRealm();
|
||||
|
||||
String policyTypeKey = getComponentTypeKey(authType);
|
||||
|
@ -113,14 +116,16 @@ public class ClientRegistrationPolicyManager {
|
|||
throw new ClientRegistrationPolicyException("Policy of type '" + policyModel.getProviderId() + "' not found");
|
||||
}
|
||||
|
||||
// TODO: trace
|
||||
logger.infof("Running policy '%s' %s", policyModel.getName(), opDescription);
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.tracef("Running policy '%s' %s", policyModel.getName(), opDescription);
|
||||
}
|
||||
|
||||
try {
|
||||
op.run(policy);
|
||||
} catch (ClientRegistrationPolicyException crpe) {
|
||||
provider.getEvent().detail(Details.CLIENT_REGISTRATION_POLICY, policyModel.getName());
|
||||
crpe.setPolicyModel(policyModel);
|
||||
logger.warnf("Operation '%s' rejected. %s", opDescription, crpe.getMessage());
|
||||
ServicesLogger.LOGGER.clientRegistrationRequestRejected(opDescription, crpe.getMessage());
|
||||
throw crpe;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,6 +33,8 @@ import org.keycloak.protocol.saml.mappers.UserAttributeStatementMapper;
|
|||
import org.keycloak.protocol.saml.mappers.UserPropertyAttributeStatementMapper;
|
||||
import org.keycloak.services.clientregistration.policy.impl.ClientTemplatesClientRegistrationPolicyFactory;
|
||||
import org.keycloak.services.clientregistration.policy.impl.ConsentRequiredClientRegistrationPolicyFactory;
|
||||
import org.keycloak.services.clientregistration.policy.impl.MaxClientsClientRegistrationPolicy;
|
||||
import org.keycloak.services.clientregistration.policy.impl.MaxClientsClientRegistrationPolicyFactory;
|
||||
import org.keycloak.services.clientregistration.policy.impl.ProtocolMappersClientRegistrationPolicyFactory;
|
||||
import org.keycloak.services.clientregistration.policy.impl.ScopeClientRegistrationPolicyFactory;
|
||||
import org.keycloak.services.clientregistration.policy.impl.TrustedHostClientRegistrationPolicyFactory;
|
||||
|
@ -87,9 +89,13 @@ public class DefaultClientRegistrationPolicies {
|
|||
ComponentModel consentRequiredModel = createModelInstance("Consent Required", realm, ConsentRequiredClientRegistrationPolicyFactory.PROVIDER_ID, policyTypeKey);
|
||||
realm.addComponentModel(consentRequiredModel);
|
||||
|
||||
ComponentModel scopeModel =createModelInstance("Full Scope Disabled", realm, ScopeClientRegistrationPolicyFactory.PROVIDER_ID, policyTypeKey);
|
||||
ComponentModel scopeModel = createModelInstance("Full Scope Disabled", realm, ScopeClientRegistrationPolicyFactory.PROVIDER_ID, policyTypeKey);
|
||||
realm.addComponentModel(scopeModel);
|
||||
|
||||
ComponentModel maxClientsModel = createModelInstance("Max Clients Limit", realm, MaxClientsClientRegistrationPolicyFactory.PROVIDER_ID, policyTypeKey);
|
||||
maxClientsModel.put(MaxClientsClientRegistrationPolicyFactory.MAX_CLIENTS, MaxClientsClientRegistrationPolicyFactory.DEFAULT_MAX_CLIENTS);
|
||||
realm.addComponentModel(maxClientsModel);
|
||||
|
||||
addGenericPolicies(realm, policyTypeKey);
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.services.clientregistration.policy.impl;
|
||||
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.services.clientregistration.ClientRegistrationContext;
|
||||
import org.keycloak.services.clientregistration.ClientRegistrationProvider;
|
||||
import org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy;
|
||||
import org.keycloak.services.clientregistration.policy.ClientRegistrationPolicyException;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class ClientDisabledClientRegistrationPolicy implements ClientRegistrationPolicy {
|
||||
|
||||
@Override
|
||||
public void beforeRegister(ClientRegistrationContext context) throws ClientRegistrationPolicyException {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterRegister(ClientRegistrationContext context, ClientModel clientModel) {
|
||||
clientModel.setEnabled(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeUpdate(ClientRegistrationContext context, ClientModel clientModel) throws ClientRegistrationPolicyException {
|
||||
if (context.getClient().isEnabled() == null) {
|
||||
return;
|
||||
}
|
||||
if (clientModel == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean isEnabled = clientModel.isEnabled();
|
||||
boolean newEnabled = context.getClient().isEnabled();
|
||||
|
||||
if (!isEnabled && newEnabled) {
|
||||
throw new ClientRegistrationPolicyException("Not permitted to enable client");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterUpdate(ClientRegistrationContext context, ClientModel clientModel) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeView(ClientRegistrationProvider provider, ClientModel clientModel) throws ClientRegistrationPolicyException {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeDelete(ClientRegistrationProvider provider, ClientModel clientModel) throws ClientRegistrationPolicyException {
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.services.clientregistration.policy.impl;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.keycloak.component.ComponentModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.provider.ProviderConfigProperty;
|
||||
import org.keycloak.services.clientregistration.policy.AbstractClientRegistrationPolicyFactory;
|
||||
import org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class ClientDisabledClientRegistrationPolicyFactory extends AbstractClientRegistrationPolicyFactory {
|
||||
|
||||
public static final String PROVIDER_ID = "client-disabled";
|
||||
|
||||
@Override
|
||||
public ClientRegistrationPolicy create(KeycloakSession session, ComponentModel model) {
|
||||
return new ClientDisabledClientRegistrationPolicy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHelpText() {
|
||||
return "When present, then newly registered client will be disabled and admin needs to manually enable them";
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ProviderConfigProperty> getConfigProperties() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return PROVIDER_ID;
|
||||
}
|
||||
|
||||
}
|
|
@ -35,8 +35,6 @@ import org.keycloak.services.clientregistration.policy.ClientRegistrationPolicyE
|
|||
*/
|
||||
public class ClientTemplatesClientRegistrationPolicy implements ClientRegistrationPolicy {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(ClientTemplatesClientRegistrationPolicy.class);
|
||||
|
||||
private final KeycloakSession session;
|
||||
private final ComponentModel componentModel;
|
||||
|
||||
|
|
|
@ -0,0 +1,77 @@
|
|||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.services.clientregistration.policy.impl;
|
||||
|
||||
import org.keycloak.component.ComponentModel;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.services.clientregistration.ClientRegistrationContext;
|
||||
import org.keycloak.services.clientregistration.ClientRegistrationProvider;
|
||||
import org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy;
|
||||
import org.keycloak.services.clientregistration.policy.ClientRegistrationPolicyException;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class MaxClientsClientRegistrationPolicy implements ClientRegistrationPolicy {
|
||||
|
||||
private final KeycloakSession session;
|
||||
private final ComponentModel componentModel;
|
||||
|
||||
public MaxClientsClientRegistrationPolicy(KeycloakSession session, ComponentModel componentModel) {
|
||||
this.session = session;
|
||||
this.componentModel = componentModel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeRegister(ClientRegistrationContext context) throws ClientRegistrationPolicyException {
|
||||
RealmModel realm = session.getContext().getRealm();
|
||||
int currentCount = realm.getClients().size();
|
||||
int maxCount = componentModel.get(MaxClientsClientRegistrationPolicyFactory.MAX_CLIENTS, MaxClientsClientRegistrationPolicyFactory.DEFAULT_MAX_CLIENTS);
|
||||
|
||||
if (currentCount >= maxCount) {
|
||||
throw new ClientRegistrationPolicyException("It's allowed to have max " + maxCount + " clients per realm");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterRegister(ClientRegistrationContext context, ClientModel clientModel) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeUpdate(ClientRegistrationContext context, ClientModel clientModel) throws ClientRegistrationPolicyException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterUpdate(ClientRegistrationContext context, ClientModel clientModel) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeView(ClientRegistrationProvider provider, ClientModel clientModel) throws ClientRegistrationPolicyException {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeDelete(ClientRegistrationProvider provider, ClientModel clientModel) throws ClientRegistrationPolicyException {
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.services.clientregistration.policy.impl;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import org.keycloak.component.ComponentModel;
|
||||
import org.keycloak.component.ComponentValidationException;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.provider.ConfigurationValidationHelper;
|
||||
import org.keycloak.provider.ProviderConfigProperty;
|
||||
import org.keycloak.services.clientregistration.policy.AbstractClientRegistrationPolicyFactory;
|
||||
import org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class MaxClientsClientRegistrationPolicyFactory extends AbstractClientRegistrationPolicyFactory {
|
||||
|
||||
public static final String MAX_CLIENTS = "max-clients";
|
||||
public static final ProviderConfigProperty MAX_CLIENTS_PROPERTY = new ProviderConfigProperty();
|
||||
|
||||
public static final int DEFAULT_MAX_CLIENTS = 200;
|
||||
|
||||
private static List<ProviderConfigProperty> configProperties = new LinkedList<>();
|
||||
|
||||
static {
|
||||
MAX_CLIENTS_PROPERTY.setName(MAX_CLIENTS);
|
||||
MAX_CLIENTS_PROPERTY.setLabel("max-clients.label");
|
||||
MAX_CLIENTS_PROPERTY.setHelpText("max-clients.tooltip");
|
||||
MAX_CLIENTS_PROPERTY.setType(ProviderConfigProperty.STRING_TYPE);
|
||||
MAX_CLIENTS_PROPERTY.setDefaultValue(String.valueOf(DEFAULT_MAX_CLIENTS));
|
||||
configProperties.add(MAX_CLIENTS_PROPERTY);
|
||||
}
|
||||
|
||||
public static final String PROVIDER_ID = "max-clients";
|
||||
|
||||
@Override
|
||||
public ClientRegistrationPolicy create(KeycloakSession session, ComponentModel model) {
|
||||
return new MaxClientsClientRegistrationPolicy(session, model);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHelpText() {
|
||||
return "When present, then it won't be allowed to register new client if count of existing clients in realm is same or bigger than configured limit";
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ProviderConfigProperty> getConfigProperties() {
|
||||
return configProperties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return PROVIDER_ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateConfiguration(KeycloakSession session, ComponentModel config) throws ComponentValidationException {
|
||||
ConfigurationValidationHelper.check(config)
|
||||
.checkInt(MAX_CLIENTS_PROPERTY, true);
|
||||
}
|
||||
}
|
|
@ -18,17 +18,15 @@
|
|||
package org.keycloak.services.clientregistration.policy.impl;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.component.ComponentModel;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.ProtocolMapperModel;
|
||||
import org.keycloak.representations.idm.ClientRepresentation;
|
||||
import org.keycloak.representations.idm.ProtocolMapperRepresentation;
|
||||
import org.keycloak.services.ServicesLogger;
|
||||
import org.keycloak.services.clientregistration.ClientRegistrationContext;
|
||||
import org.keycloak.services.clientregistration.ClientRegistrationProvider;
|
||||
import org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy;
|
||||
|
@ -66,7 +64,7 @@ public class ProtocolMappersClientRegistrationPolicy implements ClientRegistrati
|
|||
String mapperType = mapper.getProtocolMapper();
|
||||
|
||||
if (!allowedMapperProviders.contains(mapperType)) {
|
||||
logger.warnf("ProtocolMapper '%s' of type '%s' not allowed", mapper.getName(), mapperType);
|
||||
ServicesLogger.LOGGER.clientRegistrationMapperNotAllowed(mapper.getName(), mapperType);
|
||||
throw new ClientRegistrationPolicyException("ProtocolMapper type not allowed");
|
||||
}
|
||||
}
|
||||
|
@ -75,8 +73,7 @@ public class ProtocolMappersClientRegistrationPolicy implements ClientRegistrati
|
|||
|
||||
protected void enableConsentRequiredForAll(ClientModel clientModel) {
|
||||
if (isConsentRequiredForMappers()) {
|
||||
// TODO: Debug
|
||||
logger.infof("Enable consentRequired for all protocol mappers of client %s", clientModel.getClientId());
|
||||
logger.debugf("Enable consentRequired for all protocol mappers of client %s", clientModel.getClientId());
|
||||
|
||||
Set<ProtocolMapperModel> mappers = clientModel.getProtocolMappers();
|
||||
|
||||
|
@ -105,8 +102,7 @@ public class ProtocolMappersClientRegistrationPolicy implements ClientRegistrati
|
|||
|
||||
}).forEach((ProtocolMapperModel mapperToRemove) -> {
|
||||
|
||||
// TODO: debug
|
||||
logger.infof("Removing builtin mapper '%s' of type '%s' as type is not permitted", mapperToRemove.getName(), mapperToRemove.getProtocolMapper());
|
||||
logger.debugf("Removing builtin mapper '%s' of type '%s' as type is not permitted", mapperToRemove.getName(), mapperToRemove.getProtocolMapper());
|
||||
clientModel.removeProtocolMapper(mapperToRemove);
|
||||
|
||||
});
|
||||
|
|
|
@ -32,6 +32,7 @@ import org.keycloak.models.ClientModel;
|
|||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.protocol.oidc.utils.PairwiseSubMapperUtils;
|
||||
import org.keycloak.representations.idm.ClientRepresentation;
|
||||
import org.keycloak.services.ServicesLogger;
|
||||
import org.keycloak.services.clientregistration.ClientRegistrationContext;
|
||||
import org.keycloak.services.clientregistration.ClientRegistrationProvider;
|
||||
import org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy;
|
||||
|
@ -95,8 +96,7 @@ public class TrustedHostClientRegistrationPolicy implements ClientRegistrationPo
|
|||
|
||||
String hostAddress = session.getContext().getConnection().getRemoteAddr();
|
||||
|
||||
// TODO: debug
|
||||
logger.infof("Verifying remote host : %s", hostAddress);
|
||||
logger.debugf("Verifying remote host : %s", hostAddress);
|
||||
|
||||
List<String> trustedHosts = getTrustedHosts();
|
||||
List<String> trustedDomains = getTrustedDomains();
|
||||
|
@ -113,7 +113,7 @@ public class TrustedHostClientRegistrationPolicy implements ClientRegistrationPo
|
|||
return;
|
||||
}
|
||||
|
||||
logger.warnf("Failed to verify remote host : %s", hostAddress);
|
||||
ServicesLogger.LOGGER.failedToVerifyRemoteHost(hostAddress);
|
||||
throw new ClientRegistrationPolicyException("Host not trusted.");
|
||||
}
|
||||
|
||||
|
@ -154,7 +154,7 @@ public class TrustedHostClientRegistrationPolicy implements ClientRegistrationPo
|
|||
return confHostName;
|
||||
}
|
||||
} catch (UnknownHostException uhe) {
|
||||
logger.warnf("Unknown host from realm configuration: %s", confHostName);
|
||||
logger.debugf(uhe, "Unknown host from realm configuration: %s", confHostName);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -167,8 +167,7 @@ public class TrustedHostClientRegistrationPolicy implements ClientRegistrationPo
|
|||
try {
|
||||
String hostname = InetAddress.getByName(hostAddress).getHostName();
|
||||
|
||||
// TODO: Debug
|
||||
logger.infof("Trying verify request from address '%s' of host '%s' by domains", hostAddress, hostname);
|
||||
logger.debugf("Trying verify request from address '%s' of host '%s' by domains", hostAddress, hostname);
|
||||
|
||||
for (String confDomain : trustedDomains) {
|
||||
if (hostname.endsWith(confDomain)) {
|
||||
|
@ -177,7 +176,7 @@ public class TrustedHostClientRegistrationPolicy implements ClientRegistrationPo
|
|||
}
|
||||
}
|
||||
} catch (UnknownHostException uhe) {
|
||||
logger.warnf("Request of address '%s' came from unknown host. Skip verification by domains", hostAddress);
|
||||
logger.debugf(uhe, "Request of address '%s' came from unknown host. Skip verification by domains", hostAddress);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -237,11 +236,11 @@ public class TrustedHostClientRegistrationPolicy implements ClientRegistrationPo
|
|||
}
|
||||
}
|
||||
} catch (MalformedURLException mfe) {
|
||||
logger.warnf("URL '%s' is malformed", url);
|
||||
logger.debugf(mfe, "URL '%s' is malformed", url);
|
||||
throw new ClientRegistrationPolicyException("URL is malformed");
|
||||
}
|
||||
|
||||
logger.warnf("URL '%s' doesn't match any trustedHost or trustedDomain", url);
|
||||
ServicesLogger.LOGGER.urlDoesntMatch(url);
|
||||
throw new ClientRegistrationPolicyException("URL doesn't match any trusted host or trusted domain");
|
||||
}
|
||||
|
||||
|
|
|
@ -1,180 +0,0 @@
|
|||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.services.resources.admin;
|
||||
|
||||
import org.jboss.resteasy.spi.NotFoundException;
|
||||
import org.keycloak.events.admin.OperationType;
|
||||
import org.keycloak.events.admin.ResourceType;
|
||||
import org.keycloak.models.ClientRegistrationTrustedHostModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.ModelDuplicateException;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.representations.idm.ClientRegistrationTrustedHostRepresentation;
|
||||
import org.keycloak.services.ErrorResponse;
|
||||
|
||||
import javax.ws.rs.Consumes;
|
||||
import javax.ws.rs.DELETE;
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.POST;
|
||||
import javax.ws.rs.PUT;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.PathParam;
|
||||
import javax.ws.rs.Produces;
|
||||
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.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class ClientRegistrationTrustedHostResource {
|
||||
|
||||
private final RealmAuth auth;
|
||||
private final RealmModel realm;
|
||||
private final AdminEventBuilder adminEvent;
|
||||
|
||||
@Context
|
||||
protected KeycloakSession session;
|
||||
|
||||
@Context
|
||||
protected UriInfo uriInfo;
|
||||
|
||||
public ClientRegistrationTrustedHostResource(RealmModel realm, RealmAuth auth, AdminEventBuilder adminEvent) {
|
||||
this.auth = auth;
|
||||
this.realm = realm;
|
||||
this.adminEvent = adminEvent.resource(ResourceType.CLIENT_REGISTRATION_TRUSTED_HOST_MODEL);
|
||||
|
||||
auth.init(RealmAuth.Resource.CLIENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new initial access token.
|
||||
*
|
||||
* @param config
|
||||
* @return
|
||||
*/
|
||||
@POST
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public Response create(ClientRegistrationTrustedHostRepresentation config) {
|
||||
auth.requireManage();
|
||||
|
||||
if (config.getHostName() == null) {
|
||||
return ErrorResponse.error("hostName not provided in config", Response.Status.BAD_REQUEST);
|
||||
}
|
||||
|
||||
int count = config.getCount() != null ? config.getCount() : 1;
|
||||
|
||||
try {
|
||||
ClientRegistrationTrustedHostModel clientRegTrustedHostModel = session.sessions().createClientRegistrationTrustedHostModel(realm, config.getHostName(), count);
|
||||
|
||||
adminEvent.operation(OperationType.CREATE).resourcePath(uriInfo, clientRegTrustedHostModel.getHostName()).representation(config).success();
|
||||
|
||||
return Response.created(uriInfo.getAbsolutePathBuilder().path(clientRegTrustedHostModel.getHostName()).build()).build();
|
||||
} catch (ModelDuplicateException mde) {
|
||||
return ErrorResponse.exists(mde.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a new initial access token.
|
||||
*
|
||||
* @param config
|
||||
* @return
|
||||
*/
|
||||
@PUT
|
||||
@Path("{hostname}")
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public Response update(final @PathParam("hostname") String hostName, ClientRegistrationTrustedHostRepresentation config) {
|
||||
auth.requireManage();
|
||||
|
||||
if (config.getHostName() == null || !hostName.equals(config.getHostName())) {
|
||||
return ErrorResponse.error("hostName not provided in config or not compatible", Response.Status.BAD_REQUEST);
|
||||
}
|
||||
|
||||
if (config.getCount() == null) {
|
||||
return ErrorResponse.error("count needs to be available", Response.Status.BAD_REQUEST);
|
||||
}
|
||||
|
||||
if (config.getRemainingCount() != null && config.getRemainingCount() > config.getCount()) {
|
||||
return ErrorResponse.error("remainingCount can't be bigger than count", Response.Status.BAD_REQUEST);
|
||||
}
|
||||
|
||||
ClientRegistrationTrustedHostModel hostModel = session.sessions().getClientRegistrationTrustedHostModel(realm, config.getHostName());
|
||||
if (hostModel == null) {
|
||||
return ErrorResponse.error("hostName record not found", Response.Status.NOT_FOUND);
|
||||
}
|
||||
|
||||
hostModel.setCount(config.getCount());
|
||||
hostModel.setRemainingCount(config.getRemainingCount());
|
||||
|
||||
adminEvent.operation(OperationType.UPDATE).resourcePath(uriInfo).representation(config).success();
|
||||
return Response.noContent().build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an initial access token.
|
||||
*
|
||||
* @param hostName
|
||||
* @return
|
||||
*/
|
||||
@GET
|
||||
@Path("{hostname}")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public ClientRegistrationTrustedHostRepresentation getConfig(final @PathParam("hostname") String hostName) {
|
||||
auth.requireView();
|
||||
|
||||
ClientRegistrationTrustedHostModel hostModel = session.sessions().getClientRegistrationTrustedHostModel(realm, hostName);
|
||||
if (hostModel == null) {
|
||||
throw new NotFoundException("hostName record not found");
|
||||
}
|
||||
|
||||
return wrap(hostModel);
|
||||
}
|
||||
|
||||
@GET
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public List<ClientRegistrationTrustedHostRepresentation> list() {
|
||||
auth.requireView();
|
||||
|
||||
List<ClientRegistrationTrustedHostModel> models = session.sessions().listClientRegistrationTrustedHosts(realm);
|
||||
List<ClientRegistrationTrustedHostRepresentation> reps = new LinkedList<>();
|
||||
for (ClientRegistrationTrustedHostModel m : models) {
|
||||
ClientRegistrationTrustedHostRepresentation r = wrap(m);
|
||||
reps.add(r);
|
||||
}
|
||||
return reps;
|
||||
}
|
||||
|
||||
@DELETE
|
||||
@Path("{hostname}")
|
||||
public void delete(final @PathParam("hostname") String hostName) {
|
||||
auth.requireManage();
|
||||
|
||||
session.sessions().removeClientRegistrationTrustedHostModel(realm, hostName);
|
||||
adminEvent.operation(OperationType.DELETE).resourcePath(uriInfo).success();
|
||||
}
|
||||
|
||||
private ClientRegistrationTrustedHostRepresentation wrap(ClientRegistrationTrustedHostModel model) {
|
||||
return ClientRegistrationTrustedHostRepresentation.create(model.getHostName(), model.getCount(), model.getRemainingCount());
|
||||
}
|
||||
}
|
|
@ -28,6 +28,7 @@ import org.keycloak.models.utils.ModelToRepresentation;
|
|||
import org.keycloak.models.utils.RepresentationToModel;
|
||||
import org.keycloak.representations.idm.ComponentRepresentation;
|
||||
import org.keycloak.services.ErrorResponse;
|
||||
import org.keycloak.services.ErrorResponseException;
|
||||
|
||||
import javax.ws.rs.Consumes;
|
||||
import javax.ws.rs.DELETE;
|
||||
|
@ -43,9 +44,14 @@ import javax.ws.rs.core.HttpHeaders;
|
|||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.UriInfo;
|
||||
|
||||
import java.text.MessageFormat;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
|
@ -116,7 +122,7 @@ public class ComponentResource {
|
|||
adminEvent.operation(OperationType.CREATE).resourcePath(uriInfo, model.getId()).representation(rep).success();
|
||||
return Response.created(uriInfo.getAbsolutePathBuilder().path(model.getId()).build()).build();
|
||||
} catch (ComponentValidationException e) {
|
||||
return ErrorResponse.error(e.getMessage(), Response.Status.BAD_REQUEST);
|
||||
return localizedErrorResponse(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -147,7 +153,7 @@ public class ComponentResource {
|
|||
realm.updateComponent(model);
|
||||
return Response.noContent().build();
|
||||
} catch (ComponentValidationException e) {
|
||||
return ErrorResponse.error(e.getMessage(), Response.Status.BAD_REQUEST);
|
||||
return localizedErrorResponse(e);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -164,6 +170,22 @@ public class ComponentResource {
|
|||
|
||||
}
|
||||
|
||||
private Response localizedErrorResponse(ComponentValidationException cve) {
|
||||
Properties messages = AdminRoot.getMessages(session, realm, "admin-messages", auth.getAuth().getToken().getLocale());
|
||||
|
||||
Object[] localizedParameters = cve.getParameters()==null ? null : Arrays.asList(cve.getParameters()).stream().map((Object parameter) -> {
|
||||
|
||||
if (parameter instanceof String) {
|
||||
String paramStr = (String) parameter;
|
||||
return messages.getProperty(paramStr, paramStr);
|
||||
} else {
|
||||
return parameter;
|
||||
}
|
||||
|
||||
}).toArray();
|
||||
|
||||
String message = MessageFormat.format(messages.getProperty(cve.getMessage(), cve.getMessage()), localizedParameters);
|
||||
return ErrorResponse.error(message, Response.Status.BAD_REQUEST);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -19,4 +19,6 @@ org.keycloak.services.clientregistration.policy.impl.TrustedHostClientRegistrati
|
|||
org.keycloak.services.clientregistration.policy.impl.ConsentRequiredClientRegistrationPolicyFactory
|
||||
org.keycloak.services.clientregistration.policy.impl.ProtocolMappersClientRegistrationPolicyFactory
|
||||
org.keycloak.services.clientregistration.policy.impl.ClientTemplatesClientRegistrationPolicyFactory
|
||||
org.keycloak.services.clientregistration.policy.impl.ScopeClientRegistrationPolicyFactory
|
||||
org.keycloak.services.clientregistration.policy.impl.ScopeClientRegistrationPolicyFactory
|
||||
org.keycloak.services.clientregistration.policy.impl.ClientDisabledClientRegistrationPolicyFactory
|
||||
org.keycloak.services.clientregistration.policy.impl.MaxClientsClientRegistrationPolicyFactory
|
|
@ -65,7 +65,7 @@ public class ComponentsTest extends AbstractAdminTest {
|
|||
try {
|
||||
createComponent(rep);
|
||||
} catch (WebApplicationException e) {
|
||||
assertErrror(e.getResponse(), "Required is required");
|
||||
assertErrror(e.getResponse(), "'Required' is required");
|
||||
}
|
||||
|
||||
rep.getConfig().putSingle("required", "Required");
|
||||
|
@ -75,7 +75,7 @@ public class ComponentsTest extends AbstractAdminTest {
|
|||
try {
|
||||
createComponent(rep);
|
||||
} catch (WebApplicationException e) {
|
||||
assertErrror(e.getResponse(), "Number should be a number");
|
||||
assertErrror(e.getResponse(), "'Number' should be a number");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,7 +3,6 @@ package org.keycloak.testsuite.cli.registration;
|
|||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.keycloak.admin.client.resource.ClientInitialAccessResource;
|
||||
import org.keycloak.admin.client.resource.ClientRegistrationTrustedHostResource;
|
||||
import org.keycloak.admin.client.resource.RealmResource;
|
||||
import org.keycloak.authentication.authenticators.client.ClientIdAndSecretAuthenticator;
|
||||
import org.keycloak.authentication.authenticators.client.JWTClientAuthenticator;
|
||||
|
@ -12,7 +11,6 @@ import org.keycloak.client.registration.cli.config.FileConfigHandler;
|
|||
import org.keycloak.client.registration.cli.config.RealmConfigData;
|
||||
import org.keycloak.representations.idm.ClientInitialAccessCreatePresentation;
|
||||
import org.keycloak.representations.idm.ClientInitialAccessPresentation;
|
||||
import org.keycloak.representations.idm.ClientRegistrationTrustedHostRepresentation;
|
||||
import org.keycloak.representations.idm.ClientRepresentation;
|
||||
import org.keycloak.representations.idm.ComponentRepresentation;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
|
@ -27,7 +25,6 @@ import org.keycloak.testsuite.util.ClientBuilder;
|
|||
import org.keycloak.testsuite.util.UserBuilder;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
||||
import javax.ws.rs.core.Response;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
|
|
|
@ -51,7 +51,9 @@ import org.keycloak.services.clientregistration.RegistrationAccessToken;
|
|||
import org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy;
|
||||
import org.keycloak.services.clientregistration.policy.ClientRegistrationPolicyManager;
|
||||
import org.keycloak.services.clientregistration.policy.RegistrationAuth;
|
||||
import org.keycloak.services.clientregistration.policy.impl.ClientDisabledClientRegistrationPolicyFactory;
|
||||
import org.keycloak.services.clientregistration.policy.impl.ClientTemplatesClientRegistrationPolicyFactory;
|
||||
import org.keycloak.services.clientregistration.policy.impl.MaxClientsClientRegistrationPolicyFactory;
|
||||
import org.keycloak.services.clientregistration.policy.impl.ProtocolMappersClientRegistrationPolicyFactory;
|
||||
import org.keycloak.services.clientregistration.policy.impl.TrustedHostClientRegistrationPolicyFactory;
|
||||
import org.keycloak.testsuite.Assert;
|
||||
|
@ -269,6 +271,61 @@ public class ClientRegistrationPoliciesTest extends AbstractClientRegistrationTe
|
|||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testClientDisabledPolicy() throws Exception {
|
||||
setTrustedHost("localhost", getPolicyAnon());
|
||||
|
||||
// Assert new client is enabled
|
||||
OIDCClientRepresentation client = create();
|
||||
String clientId = client.getClientId();
|
||||
ClientRepresentation clientRep = ApiUtil.findClientByClientId(realmResource(), clientId).toRepresentation();
|
||||
Assert.assertTrue(clientRep.isEnabled());
|
||||
|
||||
// Add client-disabled policy
|
||||
ComponentRepresentation rep = new ComponentRepresentation();
|
||||
rep.setName("Clients disabled");
|
||||
rep.setParentId(REALM_NAME);
|
||||
rep.setProviderId(ClientDisabledClientRegistrationPolicyFactory.PROVIDER_ID);
|
||||
rep.setProviderType(ClientRegistrationPolicy.class.getName());
|
||||
rep.setSubType(getPolicyAnon());
|
||||
realmResource().components().add(rep);
|
||||
|
||||
// Assert new client is disabled
|
||||
client = create();
|
||||
clientId = client.getClientId();
|
||||
clientRep = ApiUtil.findClientByClientId(realmResource(), clientId).toRepresentation();
|
||||
Assert.assertFalse(clientRep.isEnabled());
|
||||
|
||||
// Try enable client. Should fail
|
||||
clientRep.setEnabled(true);
|
||||
assertFail(ClientRegOp.UPDATE, clientRep, 403, "Not permitted to enable client");
|
||||
|
||||
// Try update disabled client. Should pass
|
||||
clientRep.setEnabled(false);
|
||||
reg.update(clientRep);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testMaxClientsPolicy() throws Exception {
|
||||
setTrustedHost("localhost", getPolicyAnon());
|
||||
|
||||
int clientsCount = realmResource().clients().findAll().size();
|
||||
int newClientsLimit = clientsCount + 1;
|
||||
|
||||
// Allow to create one more client to current limit
|
||||
ComponentRepresentation maxClientsPolicyRep = findPolicyByProviderAndAuth(MaxClientsClientRegistrationPolicyFactory.PROVIDER_ID, getPolicyAnon());
|
||||
maxClientsPolicyRep.getConfig().putSingle(MaxClientsClientRegistrationPolicyFactory.MAX_CLIENTS, String.valueOf(newClientsLimit));
|
||||
realmResource().components().component(maxClientsPolicyRep.getId()).update(maxClientsPolicyRep);
|
||||
|
||||
// I can register one new client
|
||||
OIDCClientRepresentation client = create();
|
||||
|
||||
// I can't register more clients
|
||||
assertOidcFail(ClientRegOp.CREATE, createRepOidc(), 403, "It's allowed to have max " + newClientsLimit + " clients per realm");
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testProviders() throws Exception {
|
||||
List<ComponentTypeRepresentation> reps = realmResource().clientRegistrationPolicy().getProviders();
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.testsuite.client;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.keycloak.client.registration.ClientRegistration;
|
||||
import org.keycloak.client.registration.ClientRegistrationException;
|
||||
import org.keycloak.client.registration.HttpErrorException;
|
||||
import org.keycloak.representations.idm.ClientRepresentation;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class ClientRegistrationTester {
|
||||
|
||||
public static void main(String[] args) throws ClientRegistrationException {
|
||||
ClientRepresentation rep = createRep1();
|
||||
|
||||
ClientRegistration reg = ClientRegistration.create().url("http://localhost:8081/auth", "test").build();
|
||||
|
||||
try {
|
||||
ClientRepresentation createdRep = reg.create(rep);
|
||||
System.out.println("Created client: " + createdRep.getClientId());
|
||||
} catch (ClientRegistrationException ex) {
|
||||
HttpErrorException httpEx = (HttpErrorException) ex.getCause();
|
||||
System.err.println("HttpException when registering client. Status=" + httpEx.getStatusLine().getStatusCode() + ", Details=" + httpEx.getErrorResponse());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static ClientRepresentation createRep1() {
|
||||
ClientRepresentation rep = new ClientRepresentation();
|
||||
rep.setRedirectUris(Arrays.asList("http://localhost:8080/app"));
|
||||
rep.setDefaultRoles(new String[] { "foo-role" });
|
||||
return rep;
|
||||
}
|
||||
|
||||
}
|
|
@ -31,7 +31,6 @@ import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
|||
import org.keycloak.protocol.oidc.utils.OIDCResponseType;
|
||||
import org.keycloak.representations.idm.ClientInitialAccessCreatePresentation;
|
||||
import org.keycloak.representations.idm.ClientInitialAccessPresentation;
|
||||
import org.keycloak.representations.idm.ClientRegistrationTrustedHostRepresentation;
|
||||
import org.keycloak.representations.idm.ClientRepresentation;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.representations.oidc.OIDCClientRepresentation;
|
||||
|
|
|
@ -144,7 +144,7 @@ public class RsaKeyProviderTest extends AbstractKeycloakTest {
|
|||
rep.getConfig().putSingle(Attributes.PRIORITY_KEY, "invalid");
|
||||
|
||||
Response response = adminClient.realm("test").components().add(rep);
|
||||
assertErrror(response, "Priority should be a number");
|
||||
assertErrror(response, "'Priority' should be a number");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -156,7 +156,7 @@ public class RsaKeyProviderTest extends AbstractKeycloakTest {
|
|||
rep.getConfig().putSingle(Attributes.ENABLED_KEY, "invalid");
|
||||
|
||||
Response response = adminClient.realm("test").components().add(rep);
|
||||
assertErrror(response, "Enabled should be 'true' or 'false'");
|
||||
assertErrror(response, "'Enabled' should be 'true' or 'false'");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -168,7 +168,7 @@ public class RsaKeyProviderTest extends AbstractKeycloakTest {
|
|||
rep.getConfig().putSingle(Attributes.ACTIVE_KEY, "invalid");
|
||||
|
||||
Response response = adminClient.realm("test").components().add(rep);
|
||||
assertErrror(response, "Active should be 'true' or 'false'");
|
||||
assertErrror(response, "'Active' should be 'true' or 'false'");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -178,7 +178,7 @@ public class RsaKeyProviderTest extends AbstractKeycloakTest {
|
|||
ComponentRepresentation rep = createRep("invalid", RsaKeyProviderFactory.ID);
|
||||
|
||||
Response response = adminClient.realm("test").components().add(rep);
|
||||
assertErrror(response, "Private RSA Key is required");
|
||||
assertErrror(response, "'Private RSA Key' is required");
|
||||
|
||||
rep.getConfig().putSingle(Attributes.PRIVATE_KEY_KEY, "nonsense");
|
||||
response = adminClient.realm("test").components().add(rep);
|
||||
|
|
|
@ -316,15 +316,6 @@ public class AdminEventPaths {
|
|||
return uri.toString();
|
||||
}
|
||||
|
||||
// CLIENT REGISTRATION TRUSTED HOSTS
|
||||
|
||||
public static String clientRegistrationTrustedHostPath(String hostName) {
|
||||
URI uri = UriBuilder.fromUri("").path(RealmResource.class, "clientRegistrationTrustedHost")
|
||||
.path(ClientInitialAccessResource.class, "delete")
|
||||
.build(hostName);
|
||||
return uri.toString();
|
||||
}
|
||||
|
||||
// GROUPS
|
||||
|
||||
public static String groupsPath() {
|
||||
|
|
|
@ -67,4 +67,4 @@ log4j.logger.org.apache.directory.server.core=warn
|
|||
|
||||
# log4j.logger.org.keycloak.authentication.authenticators.browser.IdentityProviderAuthenticator=trace
|
||||
# log4j.logger.org.keycloak.keys.infinispan=trace
|
||||
log4j.logger.org.keycloak.services.clientregistration.policy=trace
|
||||
log4j.logger.org.keycloak.services.clientregistration.policy=debug
|
|
@ -543,6 +543,7 @@ initial-access-token.confirm.text=Please copy and paste the initial access token
|
|||
no-initial-access-available=No Initial Access Tokens available
|
||||
|
||||
client-reg-policies=Client Registration Policies
|
||||
client-reg-policy.name.tooltip=Display Name of the policy
|
||||
anonymous-policies=Anonymous Access Policies
|
||||
anonymous-policies.tooltip=Those Policies are used when Client Registration Service is invoked by unauthenticated request. This means request doesn't contain Initial Access Token nor Bearer Token.
|
||||
auth-policies=Authenticated Access Policies
|
||||
|
@ -561,6 +562,8 @@ consent-required-for-all-mappers.label=Consent Required For Mappers
|
|||
consent-required-for-all-mappers.tooltip=If on, then all newly registered protocol mappers will automatically have consentRequired switch on. This means that user will need to approve consent screen. NOTE: Consent screen is shown just if client has consentRequired switch on. So it's usually good to use this switch together with consent-required policy.
|
||||
allowed-client-templates.label=Allowed Client Templates
|
||||
allowed-client-templates.tooltip=Whitelist of the client templates, which can be used on newly registered client. Attempt to register client with some client template, which is not whitelisted, will be rejected. By default, the whitelist is empty, so there are not any client templates are allowed.
|
||||
max-clients.label=Max Clients Per Realm
|
||||
max-clients.tooltip=It won't be allowed to register new client if count of existing clients in realm is same or bigger than configured limit.
|
||||
|
||||
client-templates=Client Templates
|
||||
client-templates.tooltip=Client templates allow you to define common configuration that is shared between multiple clients
|
||||
|
|
Loading…
Reference in a new issue