Skip to content

Commit

Permalink
Move CORS configuration from web.xml to console (#726)
Browse files Browse the repository at this point in the history
  • Loading branch information
maximthomas authored Mar 12, 2024
1 parent 7a69146 commit fc748ea
Show file tree
Hide file tree
Showing 13 changed files with 363 additions and 291 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
* information: "Portions copyright [year] [name of copyright owner]".
*
* Copyright 2015-2016 ForgeRock AS.
* Portions copyright 2023 3A Systems LLC.
*/

package org.forgerock.openam.services.baseurl;
Expand Down Expand Up @@ -98,7 +99,7 @@ public String getRootURL(HttpContext context) {
*/
public String getRealmURL(HttpServletRequest request, String basePath, Realm realm) throws InvalidBaseUrlException {
try {
Realm dnsRealm = Realm.of(URI.create(request.getRequestURI()).getHost());
Realm dnsRealm = Realm.of(URI.create(request.getRequestURL().toString()).getHost());
return getRealmURL(getRootURL(request), basePath, dnsRealm, realm);
} catch (RealmLookupException e) {
throw new InvalidBaseUrlException(e.getMessage(), e);
Expand Down
46 changes: 46 additions & 0 deletions openam-core/src/main/resources/amCORS.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
#
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
#
# Copyright (c) 2009 Sun Microsystems Inc. All Rights Reserved
#
# The contents of this file are subject to the terms
# of the Common Development and Distribution License
# (the License). You may not use this file except in
# compliance with the License.
#
# You can obtain a copy of the License at
# https://opensso.dev.java.net/public/CDDLv1.0.html or
# opensso/legal/CDDLv1.0.txt
# See the License for the specific language governing
# permission and limitations under the License.
#
# When distributing Covered Code, include this CDDL
# Header Notice in each file and include the License file
# at opensso/legal/CDDLv1.0.txt.
# If applicable, add the following below the CDDL Header,
# with the fields enclosed by brackets [] replaced by
# your own identifying information:
# "Portions Copyrighted [year] [name of copyright owner]"
#
# $Id: amCORS.properties,v 1.2 2024/02/16 01:29:24 bigfatrat Exp $
#
# Portions Copyrighted 2024 3A Systems LLC

cors-service-description=CORS Settings
a101=Enabled
a101.help=Enable / Disable CORS
a102=Allowed Origins
a102.help=Origins from which to accept CORS requests.
a103=Accepted Methods
a103.help=HTTP methods for which to accept CORS requests.
a104=Allowed Headers
a104.help=HTTP headers which can be included in the requests.
a105=Exposed Headers
a105.help=HTTP headers which the user-agent can expose to its CORS client.
a106=Maximum Cache Age
a106.help=Maximum time that the CORS client can cache the pre-flight response, in seconds.
a107=Allow Credentials
a107.help=Whether to include the Vary (Origin) and Access-Control-Allow-Credentials headers in the response.
a108=Expected Hostname
a108A.help=The name of the host expected in the request Host header.

Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/*
* The contents of this file are subject to the terms of the Common Development and
* Distribution License (the License). You may not use this file except in compliance with the
* License.
*
* You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
* specific language governing permission and limitations under the License.
*
* When distributing Covered Software, include this CDDL Header Notice in each file and include
* the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
* Header, with the fields enclosed by brackets [] replaced by your own identifying
* information: "Portions copyright [year] [name of copyright owner]".
*
* Copyright 2024 3A Systems LLC.
*/

package org.forgerock.openam.cors;

import com.iplanet.sso.SSOToken;
import com.sun.identity.security.AdminTokenAction;
import com.sun.identity.shared.datastruct.CollectionHelper;
import com.sun.identity.shared.debug.Debug;
import com.sun.identity.sm.ServiceConfig;
import com.sun.identity.sm.ServiceConfigManager;
import com.sun.identity.sm.ServiceListener;

import java.security.AccessController;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class CORSConfigListener implements ServiceListener {

private static final Debug debug = Debug.getInstance("frRest");

private final static int DEFAULT_TIMEOUT = 600; //10 mins

private ServiceConfigManager schemaManager;

private CORSService corsService;

public CORSConfigListener() {
try {
SSOToken token = AccessController.doPrivileged(AdminTokenAction.getInstance());
ServiceConfigManager schemaManager =
new ServiceConfigManager(
"CORSService", token);

register(schemaManager);
} catch (Exception e) {
debug.error("Cannot get ServiceConfigManager - cannot register default version config listener", e);
}
}

public void register(ServiceConfigManager schemaManager) {
this.schemaManager = schemaManager;
updateSettings();
if (this.schemaManager.addListener(this) == null) {
debug.error("Could not add listener to ServiceConfigManager instance. Version behaviour changes will not "
+ "be dynamically updated");
}
}

private void updateSettings() {
try {
ServiceConfig serviceConfig = schemaManager.getGlobalConfig(null);
Map<String, Set<String>> attrs = serviceConfig.getAttributes();

final boolean enabled = CollectionHelper.getBooleanMapAttr(attrs, "cors-enabled", false);
final List<String> allowedOrigins = new ArrayList<>(attrs.get("allowed-origins"));
final List<String> acceptedMethods = new ArrayList<>(attrs.get("accepted-methods"));
final List<String> acceptedHeaders = new ArrayList<>(attrs.get("accepted-headers"));
final List<String> exposedHeaders =new ArrayList<>(attrs.get("exposed-headers"));
final String expectedHostname = CollectionHelper.getMapAttr(attrs, "expected-hostname");
final boolean allowCredentials = CollectionHelper.getBooleanMapAttr(attrs, "allow-credentials", false);

final int maxAge = CollectionHelper.getIntMapAttr(attrs, "max-age", DEFAULT_TIMEOUT, debug);
if (debug.messageEnabled()) {
debug.message("Successfully updated CORS settings");
}

corsService = new CORSService(enabled, allowedOrigins, acceptedMethods, acceptedHeaders,
exposedHeaders, maxAge, allowCredentials, expectedHostname);
} catch (Exception e) {
debug.error("Not able to set version behaviour for rest endpoints", e);
}
}


public CORSService getCorsService() {
return this.corsService;
}

@Override
public void schemaChanged(String serviceName, String version) {

}

@Override
public void globalConfigChanged(String serviceName, String version, String groupName, String serviceComponent, int type) {
updateSettings();
}

@Override
public void organizationConfigChanged(String serviceName, String version, String orgName, String groupName, String serviceComponent, int type) {

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,12 @@
* information: "Portions copyright [year] [name of copyright owner]".
*
* Copyright 2014 ForgeRock AS.
* Portions Copyrighted 2024 3A Systems LLC.
*/
package org.forgerock.openam.cors;

import java.io.IOException;
import java.util.List;
import org.forgerock.guice.core.InjectorHolder;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
Expand All @@ -25,8 +26,7 @@
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.forgerock.openam.cors.utils.CSVHelper;
import org.forgerock.util.Reject;
import java.io.IOException;

/**
* A Servlet Filter implementation of the CORS Service, initialized using a FilterConfig.
Expand All @@ -35,75 +35,17 @@
*/
public class CORSFilter implements Filter {

private CORSService service;
private final CSVHelper csvHelper = new CSVHelper();

private final static int DEFAULT_TIMEOUT = 600; //10 mins

/**
* Required default constructor
*/
public CORSFilter() { }

/**
* Testing constructor
* @param service Service through which to validate requests and alter responses applying the CORS spec.
*/
CORSFilter(final CORSService service) {
this.service = service;
}

/**
* {@inheritDoc}
*/
@Override
public void init(final FilterConfig filterConfig) throws ServletException {

Reject.ifTrue(filterConfig == null, "Configuration must not be null.");

final String allowedOrigins = filterConfig.getInitParameter(CORSConstants.ORIGINS_KEY);
final String allowedMethods = filterConfig.getInitParameter(CORSConstants.METHODS_KEY);
final String allowedHeaders = filterConfig.getInitParameter(CORSConstants.HEADERS_KEY);
final String exposedHeaderStr = filterConfig.getInitParameter(CORSConstants.EXPOSE_HEADERS_KEY);
final String expectedHostname = filterConfig.getInitParameter(CORSConstants.EXPECTED_HOSTNAME_KEY);

int maxAge = DEFAULT_TIMEOUT;
boolean allowCredentials = false;

if (allowedOrigins == null || allowedOrigins.isEmpty()) {
throw new ServletException("Invalid configuration. Allowed Origins must be set.");
}

if (allowedMethods == null || allowedMethods.isEmpty()) {
throw new ServletException("Invalid configuration. Allowed Methods must be set.");
}

final List<String> acceptedOrigins = csvHelper.csvStringToList(allowedOrigins, false);
final List<String> acceptedMethods = csvHelper.csvStringToList(allowedMethods, false);

if (!acceptedMethods.contains(CORSConstants.HTTP_OPTIONS)) {
acceptedMethods.add(CORSConstants.HTTP_OPTIONS); //always includes OPTIONS for PRE-FLIGHT flow
}

final List<String> acceptedHeaders = csvHelper.csvStringToList(allowedHeaders, true);
final List<String> exposedHeaders = csvHelper.csvStringToList(exposedHeaderStr, true);

//defaults to 0, and isn't included
if (filterConfig.getInitParameter(CORSConstants.MAX_AGE_KEY) != null) {
try {
maxAge = Integer.valueOf(filterConfig.getInitParameter(CORSConstants.MAX_AGE_KEY));
} catch (NumberFormatException e) {
throw new ServletException("Invalid configuration. Max-age must be an integer.", e);
}
}

//defaults to false
if (filterConfig.getInitParameter(CORSConstants.ALLOW_CREDENTIALS_KEY) != null) {
allowCredentials = Boolean.valueOf(filterConfig.getInitParameter(CORSConstants.ALLOW_CREDENTIALS_KEY));
}

service = new CORSService(acceptedOrigins, acceptedMethods, acceptedHeaders,
exposedHeaders, maxAge, allowCredentials, expectedHostname);
}

/**
Expand All @@ -116,17 +58,19 @@ public void doFilter(final ServletRequest request, final ServletResponse respons
final HttpServletRequest req = (HttpServletRequest) request;
final HttpServletResponse res = (HttpServletResponse) response;

if (service.handleRequest(req, res)) {
final CORSConfigListener corsConfigListener = InjectorHolder.getInstance(CORSConfigListener.class);
final CORSService corsService = corsConfigListener.getCorsService();

if (corsService == null || corsConfigListener.getCorsService().handleRequest(req, res)) {
chain.doFilter(req, res);
}

}

/**
* {@inheritDoc}
*/
@Override
public void destroy() {
service = null;

}
}
Loading

0 comments on commit fc748ea

Please sign in to comment.