KEYCLOAK-3666 client registration policies - polishing

This commit is contained in:
mposolda 2016-10-19 15:12:22 +02:00
parent 4136d76b7e
commit 3779bfb6b4
28 changed files with 66 additions and 735 deletions

View file

@ -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;
}
}

View file

@ -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);
}

View file

@ -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);
}
}

View file

@ -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 {

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -61,4 +61,6 @@ public interface Details {
String SIGNATURE_REQUIRED = "signature_required";
String SIGNATURE_ALGORITHM = "signature_algorithm";
String CLIENT_REGISTRATION_POLICY = "client_registration_policy";
}

View file

@ -128,11 +128,6 @@ public enum ResourceType {
*/
, CLIENT_INITIAL_ACCESS_MODEL
/**
*
*/
, CLIENT_REGISTRATION_TRUSTED_HOST_MODEL
/**
*
*/

View file

@ -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();
}

View file

@ -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();
}

View file

@ -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("''{0}'' should be 'true' or 'false'", label);
throw new ComponentValidationException("''{0}'' should be ''true'' or ''false''", label);
}
return this;

View file

@ -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);
}

View file

@ -170,6 +170,11 @@ public abstract class AbstractClientRegistrationProvider implements ClientRegist
this.event = event;
}
@Override
public EventBuilder getEvent() {
return event;
}
@Override
public void close() {
}

View file

@ -75,6 +75,11 @@ public class AdapterInstallationClientRegistrationProvider implements ClientRegi
this.event = event;
}
@Override
public EventBuilder getEvent() {
return event;
}
@Override
public void close() {
}

View file

@ -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;
}
}

View file

@ -31,4 +31,6 @@ public interface ClientRegistrationProvider extends Provider {
void setEvent(EventBuilder event);
EventBuilder getEvent();
}

View file

@ -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;
}
}

View file

@ -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;

View file

@ -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);
});

View file

@ -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");
}

View file

@ -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());
}
}

View file

@ -173,7 +173,7 @@ public class ComponentResource {
private Response localizedErrorResponse(ComponentValidationException cve) {
Properties messages = AdminRoot.getMessages(session, realm, "admin-messages", auth.getAuth().getToken().getLocale());
Object[] localizedParameters = Arrays.asList(cve.getParameters()).stream().map((Object parameter) -> {
Object[] localizedParameters = cve.getParameters()==null ? null : Arrays.asList(cve.getParameters()).stream().map((Object parameter) -> {
if (parameter instanceof String) {
String paramStr = (String) parameter;

View file

@ -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");
}
}

View file

@ -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;

View file

@ -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;

View file

@ -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);

View file

@ -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() {

View file

@ -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