Skip to content

Commit

Permalink
KEYCLOAK-14306 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 mposolda committed Nov 12, 2020
1 parent e8e5808 commit 01be601
Show file tree
Hide file tree
Showing 30 changed files with 812 additions and 710 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,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 @@ -1266,8 +1266,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
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
Original file line number Diff line number Diff line change
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.

Original file line number Diff line number Diff line change
@@ -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));
}
}
Original file line number Diff line number Diff line change
@@ -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();
}
Original file line number Diff line number Diff line change
@@ -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;
}
}
Loading

0 comments on commit 01be601

Please sign in to comment.