Skip to content

Commit

Permalink
KEYCLOAK-15585 OIDC redirect_uri allows dangerous schemes resulting i…
Browse files Browse the repository at this point in the history
…n potential XSS

(cherry picked from commit e86bec8)

Conflicts:
    testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/ClientRegistrationTest.java
    testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/ClientTest.java
    services/src/main/java/org/keycloak/validation/DefaultClientValidationProvider.java
  • Loading branch information
vmuzikar authored and sguilhen committed Oct 20, 2020
1 parent e40234d commit f660adc
Show file tree
Hide file tree
Showing 30 changed files with 781 additions and 694 deletions.
Expand Up @@ -29,7 +29,6 @@
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;

import org.jboss.logging.Logger;
Expand Down Expand Up @@ -136,7 +135,7 @@
import org.keycloak.storage.UserStorageProviderModel;
import org.keycloak.storage.federated.UserFederatedStorageProvider;
import org.keycloak.util.JsonSerialization;
import org.keycloak.validation.ClientValidationUtil;
import org.keycloak.validation.ValidationUtil;

public class RepresentationToModel {

Expand Down Expand Up @@ -1265,8 +1264,8 @@ private static Map<String, ClientModel> createClients(KeycloakSession session, R
ClientModel app = createClient(session, realm, resourceRep, false, mappedFlows);
appMap.put(app.getClientId(), app);

ClientValidationUtil.validate(session, app, false, c -> {
throw new RuntimeException("Invalid client " + app.getClientId() + ": " + c.getError());
ValidationUtil.validateClient(session, app, false, r -> {
throw new RuntimeException("Invalid client " + app.getClientId() + ": " + r.getAllErrorsAsString());
});
}
return appMap;
Expand Down
@@ -1,5 +1,5 @@
/*
* Copyright 2019 Red Hat, Inc. and/or its affiliates
* Copyright 2020 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");
Expand All @@ -14,26 +14,31 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.keycloak.validation;

import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.representations.oidc.OIDCClientRepresentation;

public interface ClientValidationContext {

enum Event {
CREATE,
UPDATE
/**
* @author Vaclav Muzikar <vmuzikar@redhat.com>
*/
public class ClientValidationContext extends DefaultValidationContext<ClientModel> {
public ClientValidationContext(Event event, KeycloakSession session, ClientModel objectToValidate) {
super(event, session, objectToValidate);
}

Event getEvent();

KeycloakSession getSession();
public static class OIDCContext extends ClientValidationContext {
private final OIDCClientRepresentation oidcClient;

ClientModel getClient();

String getError();

ClientValidationContext invalid(String error);
public OIDCContext(Event event, KeycloakSession session, ClientModel objectToValidate, OIDCClientRepresentation oidcClient) {
super(event, session, objectToValidate);
this.oidcClient = oidcClient;
}

public OIDCClientRepresentation getOIDCClient() {
return oidcClient;
}
}
}
Expand Up @@ -16,11 +16,12 @@
*/
package org.keycloak.validation;

import org.keycloak.provider.Provider;
import org.keycloak.models.ClientModel;

public interface ClientValidationProvider extends Provider {
public interface ClientValidationProvider extends Validator<ClientModel> {

void validate(ClientValidationContext context);
// for a special case when performing OIDC client registration
ValidationResult validate(ClientValidationContext.OIDCContext validationContext);

@Override
default void close() {
Expand Down

This file was deleted.

@@ -0,0 +1,77 @@
/*
* Copyright 2020 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.validation;

import org.keycloak.models.KeycloakSession;

import java.util.HashSet;
import java.util.Set;

/**
* @author Vaclav Muzikar <vmuzikar@redhat.com>
*/
public abstract class DefaultValidationContext<T> implements ValidationContext<T> {

private final Event event;
private final KeycloakSession session;
private final T objectToValidate;
private final Set<ValidationError> errors;

public DefaultValidationContext(Event event, KeycloakSession session, T objectToValidate) {
this.event = event;
this.session = session;
this.objectToValidate = objectToValidate;
this.errors = new HashSet<>();
}

@Override
public Event getEvent() {
return event;
}

@Override
public KeycloakSession getSession() {
return session;
}

@Override
public T getObjectToValidate() {
return objectToValidate;
}

@Override
public ValidationContext<T> addError(String message) {
return addError(null, message, null);
}

@Override
public ValidationContext<T> addError(String fieldId, String message) {
return addError(fieldId, message, null);
}

@Override
public ValidationContext<T> addError(String fieldId, String message, String localizedMessageKey, Object... localizedMessageParams) {
errors.add(new ValidationError(fieldId, message, localizedMessageKey, localizedMessageParams));
return this;
}

@Override
public ValidationResult toResult() {
return new ValidationResult(new HashSet<>(errors));
}
}
@@ -0,0 +1,39 @@
/*
* Copyright 2019 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.validation;

import org.keycloak.models.KeycloakSession;

public interface ValidationContext<T> {

enum Event {
CREATE,
UPDATE
}

Event getEvent();

KeycloakSession getSession();

T getObjectToValidate();

ValidationContext<T> addError(String message);
ValidationContext<T> addError(String fieldId, String message);
ValidationContext<T> addError(String fieldId, String message, String localizedMessageKey, Object... localizedMessageParams);

ValidationResult toResult();
}
@@ -0,0 +1,87 @@
/*
* Copyright 2020 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.validation;

import java.text.MessageFormat;
import java.util.Arrays;
import java.util.Objects;
import java.util.Properties;

/**
* @author Vaclav Muzikar <vmuzikar@redhat.com>
*/
public class ValidationError {
private final String fieldId;
private final String message;
private final String localizedMessageKey;
private final Object[] localizedMessageParameters;

public ValidationError(String fieldId, String message, String localizedMessageKey, Object[] localizedMessageParameters) {
if (message == null) {
throw new IllegalArgumentException("Message must be set");
}

this.fieldId = fieldId;
this.message = message;
this.localizedMessageKey = localizedMessageKey;
this.localizedMessageParameters = localizedMessageParameters;
}

public String getFieldId() {
return fieldId;
}

public String getLocalizedMessageKey() {
return localizedMessageKey;
}

public Object[] getLocalizedMessageParams() {
return localizedMessageParameters;
}

public String getMessage() {
return message;
}

public String getLocalizedMessage(Properties messagesBundle) {
if (getLocalizedMessageKey() != null) {
return MessageFormat.format(messagesBundle.getProperty(getLocalizedMessageKey(), getMessage()), getLocalizedMessageParams());
}
else {
return getMessage();
}
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ValidationError error = (ValidationError) o;
return Objects.equals(fieldId, error.fieldId) &&
message.equals(error.message) &&
Objects.equals(localizedMessageKey, error.localizedMessageKey) &&
Arrays.equals(localizedMessageParameters, error.localizedMessageParameters);
}

@Override
public int hashCode() {
int result = Objects.hash(fieldId, message, localizedMessageKey);
result = 31 * result + Arrays.hashCode(localizedMessageParameters);
return result;
}
}

0 comments on commit f660adc

Please sign in to comment.