Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add experimental CORS server support #519

Merged
merged 3 commits into from
Jan 23, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ public class BrooklynFeatureEnablement {
/** whether feeds are automatically registered when set on entities, so that they are persisted */
public static final String FEATURE_FEED_REGISTRATION_PROPERTY = FEATURE_PROPERTY_PREFIX+".feedRegistration";

public static final String FEATURE_CORS_CXF_PROPERTY = FEATURE_PROPERTY_PREFIX + ".corsCxfFeature";

public static final String FEATURE_CATALOG_PERSISTENCE_PROPERTY = FEATURE_PROPERTY_PREFIX+".catalogPersistence";

/** whether the default standby mode is {@link HighAvailabilityMode#HOT_STANDBY} or falling back to the traditional
Expand Down
1 change: 1 addition & 0 deletions karaf/features/src/main/feature/feature.xml
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,7 @@
<feature>brooklyn-camp-base</feature>

<feature>cxf-jaxrs</feature>
<bundle dependency="true">mvn:org.apache.cxf/cxf-rt-rs-security-cors/${cxf.version}</bundle>

<bundle dependency="true">mvn:com.fasterxml.jackson.jaxrs/jackson-jaxrs-json-provider/${fasterxml.jackson.version}</bundle>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@
import javax.annotation.Nullable;
import javax.security.auth.spi.LoginModule;

import com.google.common.collect.ImmutableList;
import org.apache.brooklyn.core.BrooklynFeatureEnablement;
import org.apache.brooklyn.rest.NopSecurityHandler;
import org.apache.brooklyn.api.location.PortRange;
import org.apache.brooklyn.api.mgmt.ManagementContext;
Expand All @@ -52,6 +54,7 @@
import org.apache.brooklyn.rest.RestApiSetup;
import org.apache.brooklyn.rest.filter.CsrfTokenFilter;
import org.apache.brooklyn.rest.filter.EntitlementContextFilter;
import org.apache.brooklyn.rest.filter.CorsImplSupplierFilter;
import org.apache.brooklyn.rest.filter.HaHotCheckResourceFilter;
import org.apache.brooklyn.rest.filter.LoggingFilter;
import org.apache.brooklyn.rest.filter.NoCacheFilter;
Expand Down Expand Up @@ -455,14 +458,21 @@ public synchronized void start() throws Exception {
}

private WebAppContext deployRestApi(WebAppContext context) {
RestApiSetup.installRest(context,
ImmutableList.Builder<Object> providersListBuilder = ImmutableList.builder();
providersListBuilder.add(
new ManagementContextProvider(),
new ShutdownHandlerProvider(shutdownHandler),
new RequestTaggingRsFilter(),
new NoCacheFilter(),
new HaHotCheckResourceFilter(),
new EntitlementContextFilter(),
new CsrfTokenFilter());
if (BrooklynFeatureEnablement.isEnabled(BrooklynFeatureEnablement.FEATURE_CORS_CXF_PROPERTY)) {
providersListBuilder.add(new CorsImplSupplierFilter(managementContext));
}

RestApiSetup.installRest(context,
providersListBuilder.build().toArray());
RestApiSetup.installServletFilters(context,
RequestTaggingFilter.class,
LoggingFilter.class);
Expand Down
5 changes: 5 additions & 0 deletions rest/rest-resources/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,11 @@
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
</dependency>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-rs-security-cors</artifactId>
<version>${cxf.version}</version>
</dependency>

<dependency>
<groupId>org.apache.brooklyn</groupId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.brooklyn.rest.filter;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.reflect.TypeToken;
import org.apache.brooklyn.api.mgmt.ManagementContext;
import org.apache.brooklyn.config.ConfigKey;
import org.apache.brooklyn.config.StringConfigMap;
import org.apache.brooklyn.core.BrooklynFeatureEnablement;
import org.apache.brooklyn.core.config.ConfigKeys;
import org.apache.brooklyn.core.config.ConfigPredicates;
import org.apache.brooklyn.core.config.ConfigUtils;
import org.apache.brooklyn.util.text.Strings;
import org.apache.cxf.rs.security.cors.CrossOriginResourceSharingFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nullable;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerResponseContext;
import javax.ws.rs.ext.Provider;
import java.util.Collections;
import java.util.List;

/**
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why repeat this? In any case, as above, just get rid of these warnings.

* <p>
* Enables support for Cross Origin Resource Sharing (CORS) filtering on Apache Brooklyn REST API.
* If enabled, the allowed origins for the CORS headers should be configured
* using the <code>brooklyn.experimental.feature.corsCxfFeature.allowedOrigins=[]</code> property.
* </p>
* <p>
* If <code>brooklyn.experimental.feature.corsCxfFeature.allowedOrigins</code> is not is not supplied then allowedOrigins will be a wildcard on all domains.<br>
* Not specifying <code>allowedOrigins</code> is strongly discouraged.
* </p>
* <p>
* Currently there is no support for varying these headers on a per-API-resource basis, that is, the same configured headers are applied to all requests.
* </p>
* <p>
* Apache Brooklyn API requests should be exposed to third party web apps with great attention.
* </p>
* Apache Brooklyn API calls do not use CORS annotations so findResourceMethod is set to false.
*/
@Provider
public class CorsImplSupplierFilter extends CrossOriginResourceSharingFilter {
/**
* @see CrossOriginResourceSharingFilter#setAllowOrigins(List<String>)
*/
public static final ConfigKey<List<String>> ALLOW_ORIGINS = ConfigKeys.newConfigKey(new TypeToken<List<String>>() {},
"allowOrigins",
"List of allowed origins. Access-Control-Allow-Origin header will be returned to client if Origin header in request is matching exactly a value among the list allowed origins. " +
"If AllowedOrigins is empty or not specified then all origins are allowed. " +
"No wildcard allowed origins are supported.",
Collections.<String>emptyList());

/**
* @see CrossOriginResourceSharingFilter#setAllowHeaders(List<String>)
*/
public static final ConfigKey<List<String>> ALLOW_HEADERS = ConfigKeys.newConfigKey(new TypeToken<List<String>>() {},
"allowHeaders",
"List of allowed headers for preflight checks.",
Collections.<String>emptyList());

/**
* @see CrossOriginResourceSharingFilter#setAllowCredentials(boolean)
*/
public static final ConfigKey<Boolean> ALLOW_CREDENTIALS = ConfigKeys.newBooleanConfigKey(
"allowCredentials",
"The value for the Access-Control-Allow-Credentials header. If false, no header is added. If true, the\n" +
" * header is added with the value 'true'. False by default.",
false);

/**
* @see CrossOriginResourceSharingFilter#setExposeHeaders(List<String>)
*/
public static final ConfigKey<List<String>> EXPOSE_HEADERS = ConfigKeys.newConfigKey(new TypeToken<List<String>>() {},
"exposeHeaders",
"A list of non-simple headers to be exposed via Access-Control-Expose-Headers.",
Collections.<String>emptyList());

/**
* @see CrossOriginResourceSharingFilter#setMaxAge(Integer)
*/
public static final ConfigKey<Integer> MAX_AGE = ConfigKeys.newIntegerConfigKey(
"maxAge",
"The value for Access-Control-Max-Age.",
null);

/**
* @see CrossOriginResourceSharingFilter#setPreflightErrorStatus(Integer)
*/
public static final ConfigKey<Integer> PREFLIGHT_FAIL_STATUS = ConfigKeys.newIntegerConfigKey(
"preflightFailStatus",
"Preflight error response status, default is 200.",
200);

public static final ConfigKey<Boolean> BLOCK_CORS_IF_UNAUTHORIZED = ConfigKeys.newBooleanConfigKey(
"blockCorsIfUnauthorized",
"Do not apply CORS if response is going to be with UNAUTHORIZED status.",
false);

private static final Logger LOGGER = LoggerFactory.getLogger(CorsImplSupplierFilter.class);

private boolean enableCors = false;

// For karaf loading
public CorsImplSupplierFilter() {}

@VisibleForTesting
public CorsImplSupplierFilter(@Nullable ManagementContext mgmt) {
Preconditions.checkNotNull(mgmt,"ManagementContext should be suppplied to CORS filter.");
setEnableCors(BrooklynFeatureEnablement.isEnabled(BrooklynFeatureEnablement.FEATURE_CORS_CXF_PROPERTY));
StringConfigMap corsProperties = ConfigUtils.filterForPrefixAndStrip(
mgmt.getConfig().submap(ConfigPredicates.nameStartsWith(BrooklynFeatureEnablement.FEATURE_CORS_CXF_PROPERTY + ".")).asMapWithStringKeys(),
BrooklynFeatureEnablement.FEATURE_CORS_CXF_PROPERTY + ".");
setAllowOrigins(corsProperties.getConfig(ALLOW_ORIGINS));
setAllowHeaders(corsProperties.getConfig(ALLOW_HEADERS));
setAllowCredentials(corsProperties.getConfig(ALLOW_CREDENTIALS));
setExposeHeaders(corsProperties.getConfig(EXPOSE_HEADERS));
setMaxAge(corsProperties.getConfig(MAX_AGE));
setPreflightErrorStatus(corsProperties.getConfig(PREFLIGHT_FAIL_STATUS));
setBlockCorsIfUnauthorized(corsProperties.getConfig(BLOCK_CORS_IF_UNAUTHORIZED));
}

public void setEnableCors(boolean enabled) {
this.enableCors = Boolean.TRUE.equals(enabled);
setFindResourceMethod(false);
if (enableCors) {
LOGGER.info("CORS brooklyn feature enabled.");
} else {
LOGGER.trace("CORS brooklyn feature disabled.");
}
}

@Override
public void setMaxAge(Integer maxAge) {
if (Integer.valueOf(-1).equals(maxAge)) {
super.setMaxAge(null);
} else {
super.setMaxAge(maxAge);
}
}

public boolean isEnableCors() {
return enableCors;
}

public void setAllowOrigins(String allowedOrigins) {
setAllowOrigins(Strings.parseCsv(allowedOrigins));
}

public void setAllowHeaders(String allowHeaders) {
setAllowHeaders(Strings.parseCsv(allowHeaders));
}

public void setExposeHeaders(String exposeHeaders) {
setExposeHeaders(Strings.parseCsv(exposeHeaders));
}

@Override
public void filter(ContainerRequestContext requestContext) {
if (enableCors) {
super.filter(requestContext);
}
}

@Override
public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) {
if (enableCors) {
super.filter(requestContext, responseContext);
}
}
}
Loading