CORS SPI
Closes #25446 Signed-off-by: Dmitry Telegin <demetrio@carretti.pro>
This commit is contained in:
parent
509f618992
commit
b0403e2268
44 changed files with 336 additions and 76 deletions
|
@ -23,7 +23,7 @@ import org.keycloak.models.RealmModel;
|
|||
import org.keycloak.quarkus.runtime.Environment;
|
||||
import org.keycloak.quarkus.runtime.configuration.Configuration;
|
||||
import org.keycloak.services.Urls;
|
||||
import org.keycloak.services.resources.Cors;
|
||||
import org.keycloak.services.cors.Cors;
|
||||
import org.keycloak.theme.FreeMarkerException;
|
||||
import org.keycloak.theme.Theme;
|
||||
import org.keycloak.theme.freemarker.FreeMarkerProvider;
|
||||
|
|
|
@ -23,6 +23,7 @@ import org.keycloak.events.EventBuilder;
|
|||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.services.cors.Cors;
|
||||
|
||||
import jakarta.ws.rs.core.HttpHeaders;
|
||||
import jakarta.ws.rs.core.MultivaluedMap;
|
||||
|
@ -40,7 +41,7 @@ public class TokenExchangeContext {
|
|||
private final MultivaluedMap<String, String> formParams;
|
||||
|
||||
// TODO: resolve deps issue and use correct types
|
||||
private final Object cors;
|
||||
private final Cors cors;
|
||||
private final Object tokenManager;
|
||||
|
||||
private final ClientModel client;
|
||||
|
@ -55,7 +56,7 @@ public class TokenExchangeContext {
|
|||
|
||||
public TokenExchangeContext(KeycloakSession session,
|
||||
MultivaluedMap<String, String> formParams,
|
||||
Object cors,
|
||||
Cors cors,
|
||||
RealmModel realm,
|
||||
EventBuilder event,
|
||||
ClientModel client,
|
||||
|
@ -83,7 +84,7 @@ public class TokenExchangeContext {
|
|||
return formParams;
|
||||
}
|
||||
|
||||
public Object getCors() {
|
||||
public Cors getCors() {
|
||||
return cors;
|
||||
}
|
||||
|
||||
|
|
92
server-spi-private/src/main/java/org/keycloak/services/cors/Cors.java
Executable file
92
server-spi-private/src/main/java/org/keycloak/services/cors/Cors.java
Executable file
|
@ -0,0 +1,92 @@
|
|||
/*
|
||||
* Copyright 2024 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.cors;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import jakarta.ws.rs.core.Response.ResponseBuilder;
|
||||
|
||||
import org.keycloak.common.util.Resteasy;
|
||||
import org.keycloak.http.HttpRequest;
|
||||
import org.keycloak.http.HttpResponse;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.provider.Provider;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public interface Cors extends Provider {
|
||||
|
||||
public static final long DEFAULT_MAX_AGE = TimeUnit.HOURS.toSeconds(1);
|
||||
public static final String DEFAULT_ALLOW_METHODS = "GET, HEAD, OPTIONS";
|
||||
public static final String DEFAULT_ALLOW_HEADERS = "Origin, Accept, X-Requested-With, Content-Type, Access-Control-Request-Method, Access-Control-Request-Headers, DPoP";
|
||||
|
||||
public static final String ORIGIN_HEADER = "Origin";
|
||||
public static final String AUTHORIZATION_HEADER = "Authorization";
|
||||
|
||||
public static final String ACCESS_CONTROL_ALLOW_ORIGIN = "Access-Control-Allow-Origin";
|
||||
public static final String ACCESS_CONTROL_ALLOW_METHODS = "Access-Control-Allow-Methods";
|
||||
public static final String ACCESS_CONTROL_ALLOW_HEADERS = "Access-Control-Allow-Headers";
|
||||
public static final String ACCESS_CONTROL_EXPOSE_HEADERS = "Access-Control-Expose-Headers";
|
||||
public static final String ACCESS_CONTROL_ALLOW_CREDENTIALS = "Access-Control-Allow-Credentials";
|
||||
public static final String ACCESS_CONTROL_MAX_AGE = "Access-Control-Max-Age";
|
||||
|
||||
public static final String ACCESS_CONTROL_ALLOW_ORIGIN_WILDCARD = "*";
|
||||
public static final String INCLUDE_REDIRECTS = "+";
|
||||
|
||||
public static Cors add(HttpRequest request, ResponseBuilder response) {
|
||||
KeycloakSession session = Resteasy.getContextData(KeycloakSession.class);
|
||||
return session.getProvider(Cors.class).request(request).builder(response);
|
||||
}
|
||||
|
||||
public static Cors add(HttpRequest request) {
|
||||
KeycloakSession session = Resteasy.getContextData(KeycloakSession.class);
|
||||
return session.getProvider(Cors.class).request(request);
|
||||
}
|
||||
|
||||
public Cors request(HttpRequest request);
|
||||
|
||||
public Cors builder(ResponseBuilder builder);
|
||||
|
||||
public Cors preflight();
|
||||
|
||||
public Cors auth();
|
||||
|
||||
public Cors allowAllOrigins();
|
||||
|
||||
public Cors allowedOrigins(KeycloakSession session, ClientModel client);
|
||||
|
||||
public Cors allowedOrigins(AccessToken token);
|
||||
|
||||
public Cors allowedOrigins(String... allowedOrigins);
|
||||
|
||||
public Cors allowedMethods(String... allowedMethods);
|
||||
|
||||
public Cors exposedHeaders(String... exposedHeaders);
|
||||
|
||||
public Response build();
|
||||
|
||||
public boolean build(HttpResponse response);
|
||||
|
||||
public boolean build(BiConsumer<String, String> addHeader);
|
||||
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* Copyright 2024 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.cors;
|
||||
|
||||
import org.keycloak.provider.ProviderFactory;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:demetrio@carretti.pro">Dmitry Telegin</a>
|
||||
*/
|
||||
public interface CorsFactory extends ProviderFactory<Cors> {
|
||||
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* Copyright 2024 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.cors;
|
||||
|
||||
import org.keycloak.provider.Provider;
|
||||
import org.keycloak.provider.ProviderFactory;
|
||||
import org.keycloak.provider.Spi;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:demetrio@carretti.pro">Dmitry Telegin</a>
|
||||
*/
|
||||
public class CorsSpi implements Spi {
|
||||
|
||||
private static final String SPI_ID = "cors";
|
||||
|
||||
@Override
|
||||
public boolean isInternal() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return SPI_ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends Provider> getProviderClass() {
|
||||
return Cors.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends ProviderFactory> getProviderFactoryClass() {
|
||||
return CorsFactory.class;
|
||||
}
|
||||
|
||||
}
|
|
@ -90,6 +90,7 @@ org.keycloak.headers.SecurityHeadersSpi
|
|||
org.keycloak.services.clientpolicy.condition.ClientPolicyConditionSpi
|
||||
org.keycloak.services.clientpolicy.executor.ClientPolicyExecutorSpi
|
||||
org.keycloak.services.clientpolicy.ClientPolicyManagerSpi
|
||||
org.keycloak.services.cors.CorsSpi
|
||||
org.keycloak.userprofile.UserProfileSpi
|
||||
org.keycloak.device.DeviceRepresentationSpi
|
||||
org.keycloak.health.LoadBalancerCheckSpi
|
||||
|
|
|
@ -91,10 +91,10 @@ import org.keycloak.representations.idm.authorization.PermissionTicketToken;
|
|||
import org.keycloak.services.CorsErrorResponseException;
|
||||
import org.keycloak.services.ErrorResponseException;
|
||||
import org.keycloak.services.Urls;
|
||||
import org.keycloak.services.cors.Cors;
|
||||
import org.keycloak.services.managers.AuthenticationManager;
|
||||
import org.keycloak.services.managers.AuthenticationSessionManager;
|
||||
import org.keycloak.services.managers.UserSessionManager;
|
||||
import org.keycloak.services.resources.Cors;
|
||||
import org.keycloak.sessions.AuthenticationSessionModel;
|
||||
import org.keycloak.sessions.RootAuthenticationSessionModel;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
|
|
@ -60,11 +60,11 @@ import org.keycloak.representations.JsonWebToken;
|
|||
import org.keycloak.saml.common.constants.GeneralConstants;
|
||||
import org.keycloak.services.CorsErrorResponseException;
|
||||
import org.keycloak.services.Urls;
|
||||
import org.keycloak.services.cors.Cors;
|
||||
import org.keycloak.services.managers.AuthenticationManager;
|
||||
import org.keycloak.services.managers.AuthenticationSessionManager;
|
||||
import org.keycloak.services.managers.BruteForceProtector;
|
||||
import org.keycloak.services.managers.UserSessionManager;
|
||||
import org.keycloak.services.resources.Cors;
|
||||
import org.keycloak.services.resources.IdentityBrokerService;
|
||||
import org.keycloak.services.resources.admin.AdminAuth;
|
||||
import org.keycloak.services.resources.admin.permissions.AdminPermissions;
|
||||
|
@ -120,7 +120,7 @@ public class DefaultTokenExchangeProvider implements TokenExchangeProvider {
|
|||
public Response exchange(TokenExchangeContext context) {
|
||||
this.formParams = context.getFormParams();
|
||||
this.session = context.getSession();
|
||||
this.cors = (Cors)context.getCors();
|
||||
this.cors = context.getCors();
|
||||
this.realm = context.getRealm();
|
||||
this.client = context.getClient();
|
||||
this.event = context.getEvent();
|
||||
|
|
|
@ -42,7 +42,7 @@ import org.keycloak.protocol.oidc.endpoints.TokenRevocationEndpoint;
|
|||
import org.keycloak.protocol.oidc.endpoints.UserInfoEndpoint;
|
||||
import org.keycloak.protocol.oidc.ext.OIDCExtProvider;
|
||||
import org.keycloak.services.CorsErrorResponseException;
|
||||
import org.keycloak.services.resources.Cors;
|
||||
import org.keycloak.services.cors.Cors;
|
||||
import org.keycloak.services.resources.RealmsResource;
|
||||
import org.keycloak.services.util.CacheControlUtil;
|
||||
|
||||
|
|
|
@ -47,8 +47,8 @@ import org.keycloak.protocol.oidc.utils.RedirectUtils;
|
|||
import org.keycloak.services.CorsErrorResponseException;
|
||||
import org.keycloak.services.ErrorPageException;
|
||||
import org.keycloak.services.ServicesLogger;
|
||||
import org.keycloak.services.cors.Cors;
|
||||
import org.keycloak.services.messages.Messages;
|
||||
import org.keycloak.services.resources.Cors;
|
||||
import org.keycloak.sessions.AuthenticationSessionModel;
|
||||
import org.keycloak.util.TokenUtil;
|
||||
import org.keycloak.utils.StringUtil;
|
||||
|
|
|
@ -64,12 +64,12 @@ import org.keycloak.services.ErrorPage;
|
|||
import org.keycloak.services.ErrorResponseException;
|
||||
import org.keycloak.services.clientpolicy.ClientPolicyException;
|
||||
import org.keycloak.services.clientpolicy.context.LogoutRequestContext;
|
||||
import org.keycloak.services.cors.Cors;
|
||||
import org.keycloak.services.managers.AuthenticationManager;
|
||||
import org.keycloak.services.managers.AuthenticationSessionManager;
|
||||
import org.keycloak.services.managers.ClientSessionCode;
|
||||
import org.keycloak.services.managers.UserSessionManager;
|
||||
import org.keycloak.services.messages.Messages;
|
||||
import org.keycloak.services.resources.Cors;
|
||||
import org.keycloak.services.resources.LogoutSessionCodeChecks;
|
||||
import org.keycloak.services.resources.SessionCodeChecks;
|
||||
import org.keycloak.services.util.LocaleUtil;
|
||||
|
|
|
@ -85,13 +85,13 @@ import org.keycloak.services.clientpolicy.context.TokenRefreshContext;
|
|||
import org.keycloak.services.clientpolicy.context.TokenRefreshResponseContext;
|
||||
import org.keycloak.services.clientpolicy.context.TokenRequestContext;
|
||||
import org.keycloak.services.clientpolicy.context.TokenResponseContext;
|
||||
import org.keycloak.services.cors.Cors;
|
||||
import org.keycloak.services.managers.AppAuthManager;
|
||||
import org.keycloak.services.managers.AuthenticationManager;
|
||||
import org.keycloak.services.managers.AuthenticationSessionManager;
|
||||
import org.keycloak.services.managers.ClientManager;
|
||||
import org.keycloak.services.managers.RealmManager;
|
||||
import org.keycloak.services.managers.UserSessionManager;
|
||||
import org.keycloak.services.resources.Cors;
|
||||
import org.keycloak.services.util.AuthorizationContextUtil;
|
||||
import org.keycloak.services.util.DefaultClientSessionContext;
|
||||
import org.keycloak.services.util.DPoPUtil;
|
||||
|
|
|
@ -51,10 +51,10 @@ import org.keycloak.services.CorsErrorResponseException;
|
|||
import org.keycloak.services.clientpolicy.ClientPolicyException;
|
||||
import org.keycloak.services.clientpolicy.context.TokenRevokeContext;
|
||||
import org.keycloak.services.clientpolicy.context.TokenRevokeResponseContext;
|
||||
import org.keycloak.services.cors.Cors;
|
||||
import org.keycloak.services.managers.UserConsentManager;
|
||||
import org.keycloak.services.managers.UserSessionCrossDCManager;
|
||||
import org.keycloak.services.managers.UserSessionManager;
|
||||
import org.keycloak.services.resources.Cors;
|
||||
import org.keycloak.util.TokenUtil;
|
||||
|
||||
/**
|
||||
|
|
|
@ -56,8 +56,8 @@ import org.keycloak.representations.dpop.DPoP;
|
|||
import org.keycloak.services.Urls;
|
||||
import org.keycloak.services.clientpolicy.ClientPolicyException;
|
||||
import org.keycloak.services.clientpolicy.context.UserInfoRequestContext;
|
||||
import org.keycloak.services.cors.Cors;
|
||||
import org.keycloak.services.managers.AppAuthManager;
|
||||
import org.keycloak.services.resources.Cors;
|
||||
import org.keycloak.services.util.DPoPUtil;
|
||||
import org.keycloak.services.util.DefaultClientSessionContext;
|
||||
import org.keycloak.services.util.MtlsHoKTokenUtil;
|
||||
|
|
|
@ -54,9 +54,9 @@ import org.keycloak.services.CorsErrorResponseException;
|
|||
import org.keycloak.services.ErrorResponseException;
|
||||
import org.keycloak.services.Urls;
|
||||
import org.keycloak.services.clientpolicy.ClientPolicyException;
|
||||
import org.keycloak.services.cors.Cors;
|
||||
import org.keycloak.services.managers.AuthenticationManager;
|
||||
import org.keycloak.services.managers.UserConsentManager;
|
||||
import org.keycloak.services.resources.Cors;
|
||||
import org.keycloak.services.util.DefaultClientSessionContext;
|
||||
import org.keycloak.sessions.AuthenticationSessionModel;
|
||||
import org.keycloak.sessions.RootAuthenticationSessionModel;
|
||||
|
|
|
@ -29,7 +29,7 @@ import org.keycloak.models.ClientModel;
|
|||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.services.resources.Cors;
|
||||
import org.keycloak.services.cors.Cors;
|
||||
import org.keycloak.util.TokenUtil;
|
||||
|
||||
/**
|
||||
|
|
|
@ -49,9 +49,9 @@ import org.keycloak.protocol.oidc.utils.PkceUtils;
|
|||
import org.keycloak.services.CorsErrorResponseException;
|
||||
import org.keycloak.services.ErrorResponseException;
|
||||
import org.keycloak.services.clientpolicy.ClientPolicyException;
|
||||
import org.keycloak.services.cors.Cors;
|
||||
import org.keycloak.services.managers.AuthenticationManager;
|
||||
import org.keycloak.services.managers.UserSessionCrossDCManager;
|
||||
import org.keycloak.services.resources.Cors;
|
||||
import org.keycloak.services.resources.RealmsResource;
|
||||
import org.keycloak.services.util.DefaultClientSessionContext;
|
||||
import org.keycloak.sessions.AuthenticationSessionModel;
|
||||
|
|
|
@ -50,9 +50,9 @@ import org.keycloak.services.ErrorResponseException;
|
|||
import org.keycloak.services.ServicesLogger;
|
||||
import org.keycloak.services.Urls;
|
||||
import org.keycloak.services.clientpolicy.ClientPolicyException;
|
||||
import org.keycloak.services.cors.Cors;
|
||||
import org.keycloak.services.messages.Messages;
|
||||
import org.keycloak.services.resource.RealmResourceProvider;
|
||||
import org.keycloak.services.resources.Cors;
|
||||
import org.keycloak.services.util.CacheControlUtil;
|
||||
import org.keycloak.sessions.AuthenticationSessionModel;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
|
|
@ -31,7 +31,7 @@ import org.keycloak.models.KeycloakSession;
|
|||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.protocol.oidc.utils.AuthorizeClientUtil;
|
||||
import org.keycloak.services.CorsErrorResponseException;
|
||||
import org.keycloak.services.resources.Cors;
|
||||
import org.keycloak.services.cors.Cors;
|
||||
|
||||
public abstract class AbstractParEndpoint {
|
||||
|
||||
|
|
|
@ -32,7 +32,7 @@ import org.keycloak.protocol.oidc.par.ParResponse;
|
|||
import org.keycloak.protocol.oidc.par.clientpolicy.context.PushedAuthorizationRequestContext;
|
||||
import org.keycloak.protocol.oidc.par.endpoints.request.ParEndpointRequestParserProcessor;
|
||||
import org.keycloak.services.clientpolicy.ClientPolicyException;
|
||||
import org.keycloak.services.resources.Cors;
|
||||
import org.keycloak.services.cors.Cors;
|
||||
import org.keycloak.utils.ProfileHelper;
|
||||
|
||||
import jakarta.ws.rs.Consumes;
|
||||
|
|
|
@ -31,7 +31,7 @@ import org.keycloak.models.RealmModel;
|
|||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||
import org.keycloak.services.CorsErrorResponseException;
|
||||
import org.keycloak.services.ErrorResponseException;
|
||||
import org.keycloak.services.resources.Cors;
|
||||
import org.keycloak.services.cors.Cors;
|
||||
|
||||
import jakarta.ws.rs.WebApplicationException;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
|
|
|
@ -9,7 +9,7 @@ import org.keycloak.events.Errors;
|
|||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||
import org.keycloak.services.CorsErrorResponseException;
|
||||
import org.keycloak.events.EventBuilder;
|
||||
import org.keycloak.services.resources.Cors;
|
||||
import org.keycloak.services.cors.Cors;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
package org.keycloak.services;
|
||||
|
||||
import org.keycloak.representations.idm.OAuth2ErrorRepresentation;
|
||||
import org.keycloak.services.resources.Cors;
|
||||
import org.keycloak.services.cors.Cors;
|
||||
|
||||
import jakarta.ws.rs.WebApplicationException;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* Copyright 2024 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.cors;
|
||||
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:demetrio@carretti.pro">Dmitry Telegin</a>
|
||||
*/
|
||||
public class DefaultCorsFactory implements CorsFactory {
|
||||
|
||||
private static final String PROVIDER_ID = "default";
|
||||
|
||||
@Override
|
||||
public Cors create(KeycloakSession session) {
|
||||
return new DefaultCorsImpl(session.getContext().getHttpRequest());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Config.Scope config) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postInit(KeycloakSessionFactory factory) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return PROVIDER_ID;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* Copyright 2024 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");
|
||||
|
@ -14,16 +14,18 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.services.resources;
|
||||
|
||||
package org.keycloak.services.cors;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import jakarta.ws.rs.core.Response.ResponseBuilder;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.http.HttpRequest;
|
||||
import org.keycloak.common.util.CollectionUtil;
|
||||
|
@ -36,26 +38,9 @@ import org.keycloak.representations.AccessToken;
|
|||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class Cors {
|
||||
public class DefaultCorsImpl implements Cors {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(Cors.class);
|
||||
|
||||
public static final long DEFAULT_MAX_AGE = TimeUnit.HOURS.toSeconds(1);
|
||||
public static final String DEFAULT_ALLOW_METHODS = "GET, HEAD, OPTIONS";
|
||||
public static final String DEFAULT_ALLOW_HEADERS = "Origin, Accept, X-Requested-With, Content-Type, Access-Control-Request-Method, Access-Control-Request-Headers, DPoP";
|
||||
|
||||
public static final String ORIGIN_HEADER = "Origin";
|
||||
public static final String AUTHORIZATION_HEADER = "Authorization";
|
||||
|
||||
public static final String ACCESS_CONTROL_ALLOW_ORIGIN = "Access-Control-Allow-Origin";
|
||||
public static final String ACCESS_CONTROL_ALLOW_METHODS = "Access-Control-Allow-Methods";
|
||||
public static final String ACCESS_CONTROL_ALLOW_HEADERS = "Access-Control-Allow-Headers";
|
||||
public static final String ACCESS_CONTROL_EXPOSE_HEADERS = "Access-Control-Expose-Headers";
|
||||
public static final String ACCESS_CONTROL_ALLOW_CREDENTIALS = "Access-Control-Allow-Credentials";
|
||||
public static final String ACCESS_CONTROL_MAX_AGE = "Access-Control-Max-Age";
|
||||
|
||||
public static final String ACCESS_CONTROL_ALLOW_ORIGIN_WILDCARD = "*";
|
||||
public static final String INCLUDE_REDIRECTS = "+";
|
||||
private static final Logger logger = Logger.getLogger(DefaultCorsImpl.class);
|
||||
|
||||
private HttpRequest request;
|
||||
private ResponseBuilder builder;
|
||||
|
@ -66,43 +51,41 @@ public class Cors {
|
|||
private boolean preflight;
|
||||
private boolean auth;
|
||||
|
||||
public Cors(HttpRequest request, ResponseBuilder response) {
|
||||
this.request = request;
|
||||
this.builder = response;
|
||||
}
|
||||
|
||||
public Cors(HttpRequest request) {
|
||||
DefaultCorsImpl(HttpRequest request) {
|
||||
this.request = request;
|
||||
}
|
||||
|
||||
public static Cors add(HttpRequest request, ResponseBuilder response) {
|
||||
return new Cors(request, response);
|
||||
}
|
||||
|
||||
public static Cors add(HttpRequest request) {
|
||||
return new Cors(request);
|
||||
@Override
|
||||
public Cors request(HttpRequest request) {
|
||||
this.request = request;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cors builder(ResponseBuilder builder) {
|
||||
this.builder = builder;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cors preflight() {
|
||||
preflight = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cors auth() {
|
||||
auth = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cors allowAllOrigins() {
|
||||
allowedOrigins = Collections.singleton(ACCESS_CONTROL_ALLOW_ORIGIN_WILDCARD);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cors allowedOrigins(KeycloakSession session, ClientModel client) {
|
||||
if (client != null) {
|
||||
allowedOrigins = WebOriginsUtils.resolveValidWebOrigins(session, client);
|
||||
|
@ -110,6 +93,7 @@ public class Cors {
|
|||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cors allowedOrigins(AccessToken token) {
|
||||
if (token != null) {
|
||||
allowedOrigins = token.getAllowedOrigins();
|
||||
|
@ -117,6 +101,7 @@ public class Cors {
|
|||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cors allowedOrigins(String... allowedOrigins) {
|
||||
if (allowedOrigins != null && allowedOrigins.length > 0) {
|
||||
this.allowedOrigins = new HashSet<>(Arrays.asList(allowedOrigins));
|
||||
|
@ -124,39 +109,56 @@ public class Cors {
|
|||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cors allowedMethods(String... allowedMethods) {
|
||||
this.allowedMethods = new HashSet<>(Arrays.asList(allowedMethods));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cors exposedHeaders(String... exposedHeaders) {
|
||||
this.exposedHeaders = new HashSet<>(Arrays.asList(exposedHeaders));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response build() {
|
||||
build(builder::header);
|
||||
if (builder == null) {
|
||||
throw new IllegalStateException("builder is not set");
|
||||
}
|
||||
|
||||
if (build(builder::header)) {
|
||||
logger.debug("Added CORS headers to response");
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
public void build(HttpResponse response) {
|
||||
build(response::addHeader);
|
||||
@Override
|
||||
public boolean build(HttpResponse response) {
|
||||
if (build(response::addHeader)) {
|
||||
logger.debug("Added CORS headers to response");
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean build(BiConsumer<String, String> addHeader) {
|
||||
if (request == null) {
|
||||
throw new IllegalStateException("request is not set");
|
||||
}
|
||||
|
||||
public void build(BiConsumer<String, String> addHeader) {
|
||||
String origin = request.getHttpHeaders().getRequestHeaders().getFirst(ORIGIN_HEADER);
|
||||
if (origin == null) {
|
||||
logger.trace("No origin header ignoring");
|
||||
return;
|
||||
logger.trace("No Origin header, ignoring");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!preflight && (allowedOrigins == null || (!allowedOrigins.contains(origin) && !allowedOrigins.contains(ACCESS_CONTROL_ALLOW_ORIGIN_WILDCARD)))) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debugv("Invalid CORS request: origin {0} not in allowed origins {1}", origin, allowedOrigins);
|
||||
}
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
addHeader.accept(ACCESS_CONTROL_ALLOW_ORIGIN, origin);
|
||||
|
@ -186,6 +188,12 @@ public class Cors {
|
|||
if (preflight) {
|
||||
addHeader.accept(ACCESS_CONTROL_MAX_AGE, String.valueOf(DEFAULT_MAX_AGE));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
|
||||
}
|
|
@ -82,6 +82,7 @@ import org.keycloak.services.managers.AuthenticationSessionManager;
|
|||
import org.keycloak.services.managers.BruteForceProtector;
|
||||
import org.keycloak.services.managers.ClientSessionCode;
|
||||
import org.keycloak.services.messages.Messages;
|
||||
import org.keycloak.services.cors.Cors;
|
||||
import org.keycloak.services.resources.account.AccountConsole;
|
||||
import org.keycloak.services.util.AuthenticationFlowURLHelper;
|
||||
import org.keycloak.services.util.BrowserHistoryHelper;
|
||||
|
|
|
@ -21,6 +21,7 @@ import org.keycloak.common.Version;
|
|||
import org.keycloak.encoding.ResourceEncodingHelper;
|
||||
import org.keycloak.encoding.ResourceEncodingProvider;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.services.cors.Cors;
|
||||
import org.keycloak.services.util.CacheControlUtil;
|
||||
import org.keycloak.utils.MediaType;
|
||||
|
||||
|
|
|
@ -25,6 +25,7 @@ import org.keycloak.models.KeycloakSession;
|
|||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
|
||||
import org.keycloak.representations.idm.PublishedRealmRepresentation;
|
||||
import org.keycloak.services.cors.Cors;
|
||||
import org.keycloak.services.Urls;
|
||||
|
||||
import jakarta.ws.rs.GET;
|
||||
|
|
|
@ -31,6 +31,7 @@ import org.keycloak.protocol.LoginProtocol;
|
|||
import org.keycloak.protocol.LoginProtocolFactory;
|
||||
import org.keycloak.services.CorsErrorResponseException;
|
||||
import org.keycloak.services.clientregistration.ClientRegistrationService;
|
||||
import org.keycloak.services.cors.Cors;
|
||||
import org.keycloak.services.managers.RealmManager;
|
||||
import org.keycloak.services.resource.RealmResourceProvider;
|
||||
import org.keycloak.services.resources.account.AccountLoader;
|
||||
|
|
|
@ -30,6 +30,7 @@ import org.keycloak.encoding.ResourceEncodingProvider;
|
|||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.services.ServicesLogger;
|
||||
import org.keycloak.services.cors.Cors;
|
||||
import org.keycloak.services.util.CacheControlUtil;
|
||||
import org.keycloak.services.util.LocaleUtil;
|
||||
import org.keycloak.theme.Theme;
|
||||
|
|
|
@ -26,11 +26,11 @@ import org.keycloak.models.ClientModel;
|
|||
import org.keycloak.models.Constants;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.services.cors.Cors;
|
||||
import org.keycloak.services.managers.AppAuthManager;
|
||||
import org.keycloak.services.managers.Auth;
|
||||
import org.keycloak.services.managers.AuthenticationManager;
|
||||
import org.keycloak.services.resource.AccountResourceProvider;
|
||||
import org.keycloak.services.resources.Cors;
|
||||
import org.keycloak.theme.Theme;
|
||||
|
||||
import jakarta.ws.rs.HttpMethod;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package org.keycloak.services.resources.account;
|
||||
|
||||
import org.keycloak.http.HttpRequest;
|
||||
import org.keycloak.services.resources.Cors;
|
||||
import org.keycloak.services.cors.Cors;
|
||||
|
||||
import jakarta.ws.rs.OPTIONS;
|
||||
import jakarta.ws.rs.Path;
|
||||
|
|
|
@ -56,9 +56,9 @@ import org.keycloak.representations.account.AccountLinkUriRepresentation;
|
|||
import org.keycloak.representations.account.LinkedAccountRepresentation;
|
||||
import org.keycloak.services.ErrorResponse;
|
||||
import org.keycloak.services.Urls;
|
||||
import org.keycloak.services.cors.Cors;
|
||||
import org.keycloak.services.managers.Auth;
|
||||
import org.keycloak.services.messages.Messages;
|
||||
import org.keycloak.services.resources.Cors;
|
||||
import org.keycloak.services.validation.Validation;
|
||||
|
||||
import static org.keycloak.models.Constants.ACCOUNT_CONSOLE_CLIENT_ID;
|
||||
|
|
|
@ -36,11 +36,11 @@ import org.keycloak.models.RoleModel;
|
|||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
|
||||
import org.keycloak.services.Urls;
|
||||
import org.keycloak.services.cors.Cors;
|
||||
import org.keycloak.services.managers.AppAuthManager;
|
||||
import org.keycloak.services.managers.AuthenticationManager;
|
||||
import org.keycloak.services.managers.ClientManager;
|
||||
import org.keycloak.services.managers.RealmManager;
|
||||
import org.keycloak.services.resources.Cors;
|
||||
import org.keycloak.theme.FreeMarkerException;
|
||||
import org.keycloak.theme.Theme;
|
||||
import org.keycloak.theme.freemarker.FreeMarkerProvider;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package org.keycloak.services.resources.admin;
|
||||
|
||||
import org.keycloak.http.HttpRequest;
|
||||
import org.keycloak.services.resources.Cors;
|
||||
import org.keycloak.services.cors.Cors;
|
||||
|
||||
import jakarta.ws.rs.OPTIONS;
|
||||
import jakarta.ws.rs.Path;
|
||||
|
|
|
@ -30,10 +30,10 @@ import org.keycloak.models.RealmModel;
|
|||
import org.keycloak.protocol.oidc.TokenManager;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.services.ForbiddenException;
|
||||
import org.keycloak.services.cors.Cors;
|
||||
import org.keycloak.services.managers.AppAuthManager;
|
||||
import org.keycloak.services.managers.AuthenticationManager;
|
||||
import org.keycloak.services.managers.RealmManager;
|
||||
import org.keycloak.services.resources.Cors;
|
||||
import org.keycloak.services.resources.admin.info.ServerInfoAdminResource;
|
||||
import org.keycloak.services.resources.admin.permissions.AdminPermissions;
|
||||
import org.keycloak.theme.Theme;
|
||||
|
|
|
@ -23,7 +23,7 @@ import jakarta.ws.rs.core.Response;
|
|||
import org.keycloak.http.HttpRequest;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.protocol.oidc.TokenManager;
|
||||
import org.keycloak.services.resources.Cors;
|
||||
import org.keycloak.services.cors.Cors;
|
||||
|
||||
public class RealmsAdminResourcePreflight extends RealmsAdminResource {
|
||||
|
||||
|
|
|
@ -38,7 +38,7 @@ import jakarta.ws.rs.core.Response;
|
|||
import org.keycloak.OAuthErrorException;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.representations.idm.OAuth2ErrorRepresentation;
|
||||
import org.keycloak.services.resources.Cors;
|
||||
import org.keycloak.services.cors.Cors;
|
||||
|
||||
import static jakarta.ws.rs.core.HttpHeaders.WWW_AUTHENTICATE;
|
||||
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
#
|
||||
# Copyright 2024 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.
|
||||
#
|
||||
#
|
||||
|
||||
org.keycloak.services.cors.DefaultCorsFactory
|
|
@ -7,7 +7,7 @@ import org.apache.http.impl.client.HttpClientBuilder;
|
|||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.services.resources.Cors;
|
||||
import org.keycloak.services.cors.Cors;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
|
|
|
@ -77,7 +77,7 @@ import org.keycloak.representations.oidc.TokenMetadataRepresentation;
|
|||
import org.keycloak.services.clientpolicy.ClientPolicyException;
|
||||
import org.keycloak.services.clientpolicy.condition.ClientAccessTypeConditionFactory;
|
||||
import org.keycloak.services.clientpolicy.executor.DPoPBindEnforcerExecutorFactory;
|
||||
import org.keycloak.services.resources.Cors;
|
||||
import org.keycloak.services.cors.Cors;
|
||||
import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
|
||||
import org.keycloak.testsuite.Assert;
|
||||
import org.keycloak.testsuite.AssertEvents;
|
||||
|
|
|
@ -41,7 +41,7 @@ import org.keycloak.representations.IDToken;
|
|||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.services.clientregistration.ClientRegistrationService;
|
||||
import org.keycloak.services.clientregistration.oidc.OIDCClientRegistrationProviderFactory;
|
||||
import org.keycloak.services.resources.Cors;
|
||||
import org.keycloak.services.cors.Cors;
|
||||
import org.keycloak.services.resources.RealmsResource;
|
||||
import org.keycloak.testsuite.AbstractKeycloakTest;
|
||||
import org.keycloak.testsuite.Assert;
|
||||
|
|
Loading…
Reference in a new issue