diff options
author | Roberto E. Escobar | 2014-07-21 21:24:46 +0000 |
---|---|---|
committer | Roberto E. Escobar | 2014-08-28 23:59:32 +0000 |
commit | e9a88df4a86225a3c830a579f2ca4cc0cea77cf9 (patch) | |
tree | ac0813f2160b38f34eb28283c9b0ae651a50f9c3 /plugins/org.eclipse.osee.jaxrs.server | |
parent | af2a22771bbc0812250308ce30c6fbbce79d8f91 (diff) | |
download | org.eclipse.osee-e9a88df4a86225a3c830a579f2ca4cc0cea77cf9.tar.gz org.eclipse.osee-e9a88df4a86225a3c830a579f2ca4cc0cea77cf9.tar.xz org.eclipse.osee-e9a88df4a86225a3c830a579f2ca4cc0cea77cf9.zip |
feature[ats_ATS77367]: Create OAuth2 resource server filter
Change-Id: Iafd5a3d8733e652342bcdb299eb55d8fa746c98f
Diffstat (limited to 'plugins/org.eclipse.osee.jaxrs.server')
6 files changed, 462 insertions, 1 deletions
diff --git a/plugins/org.eclipse.osee.jaxrs.server/META-INF/MANIFEST.MF b/plugins/org.eclipse.osee.jaxrs.server/META-INF/MANIFEST.MF index c012d88e412..f8995041ee6 100644 --- a/plugins/org.eclipse.osee.jaxrs.server/META-INF/MANIFEST.MF +++ b/plugins/org.eclipse.osee.jaxrs.server/META-INF/MANIFEST.MF @@ -5,12 +5,14 @@ Bundle-SymbolicName: org.eclipse.osee.jaxrs.server Bundle-Version: 0.18.0.qualifier Bundle-RequiredExecutionEnvironment: JavaSE-1.6 Service-Component: OSGI-INF/*.xml -Import-Package: com.google.common.io, +Import-Package: com.google.common.cache, + com.google.common.io, javax.annotation;version="1.2.0", javax.annotation.security;version="1.1.0", javax.servlet;version="2.6.0", javax.servlet.http;version="2.6.0", javax.ws.rs;version="2.0.0", + javax.ws.rs.client;version="2.0.0", javax.ws.rs.container;version="2.0.0", javax.ws.rs.core;version="2.0.0", javax.ws.rs.ext;version="2.0.0", @@ -58,6 +60,7 @@ Import-Package: com.google.common.io, org.eclipse.osee.framework.jdk.core.type, org.eclipse.osee.framework.jdk.core.util, org.eclipse.osee.jaxrs, + org.eclipse.osee.jaxrs.client, org.eclipse.osee.logger, org.eclipse.osee.template.engine, org.osgi.framework, diff --git a/plugins/org.eclipse.osee.jaxrs.server/OSGI-INF/jaxrs.security.oauth2.resource.server.xml b/plugins/org.eclipse.osee.jaxrs.server/OSGI-INF/jaxrs.security.oauth2.resource.server.xml new file mode 100644 index 00000000000..87dc34a872f --- /dev/null +++ b/plugins/org.eclipse.osee.jaxrs.server/OSGI-INF/jaxrs.security.oauth2.resource.server.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="UTF-8"?> +<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="start" configuration-policy="require" deactivate="stop" modified="update"> + <implementation class="org.eclipse.osee.jaxrs.server.internal.security.oauth2.rs.OAuth2ResourceServer" /> + <reference bind="setJaxRsApplicationRegistry" cardinality="1..1" interface="org.eclipse.osee.jaxrs.server.internal.applications.JaxRsApplicationRegistry" name="JaxRsApplicationRegistry" policy="static"/> +</scr:component> diff --git a/plugins/org.eclipse.osee.jaxrs.server/src/org/eclipse/osee/jaxrs/server/internal/JaxRsUtils.java b/plugins/org.eclipse.osee.jaxrs.server/src/org/eclipse/osee/jaxrs/server/internal/JaxRsUtils.java index d7a28684e45..03b4b003147 100644 --- a/plugins/org.eclipse.osee.jaxrs.server/src/org/eclipse/osee/jaxrs/server/internal/JaxRsUtils.java +++ b/plugins/org.eclipse.osee.jaxrs.server/src/org/eclipse/osee/jaxrs/server/internal/JaxRsUtils.java @@ -115,6 +115,15 @@ public final class JaxRsUtils { return collection == null || collection.isEmpty(); } + public static int getInt(Map<String, Object> props, String key, int defaultValue) { + int toReturn = defaultValue; + String value = get(props, key, null); + if (value != null && Strings.isNumeric(value)) { + toReturn = Integer.parseInt(value); + } + return toReturn; + } + public static long getLong(Map<String, Object> props, String key, long defaultValue) { long toReturn = defaultValue; String value = get(props, key, null); diff --git a/plugins/org.eclipse.osee.jaxrs.server/src/org/eclipse/osee/jaxrs/server/internal/security/oauth2/rs/OAuth2ResourceServer.java b/plugins/org.eclipse.osee.jaxrs.server/src/org/eclipse/osee/jaxrs/server/internal/security/oauth2/rs/OAuth2ResourceServer.java new file mode 100644 index 00000000000..3fb6c86650d --- /dev/null +++ b/plugins/org.eclipse.osee.jaxrs.server/src/org/eclipse/osee/jaxrs/server/internal/security/oauth2/rs/OAuth2ResourceServer.java @@ -0,0 +1,115 @@ +/******************************************************************************* + * Copyright (c) 2014 Boeing. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Boeing - initial API and implementation + *******************************************************************************/ +package org.eclipse.osee.jaxrs.server.internal.security.oauth2.rs; + +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; +import org.eclipse.osee.jaxrs.server.internal.JaxRsConstants; +import org.eclipse.osee.jaxrs.server.internal.applications.JaxRsApplicationRegistry; +import org.eclipse.osee.jaxrs.server.security.JaxRsOAuth; +import org.eclipse.osee.jaxrs.server.security.JaxRsOAuthResourceServerFilter; +import org.eclipse.osee.jaxrs.server.security.JaxRsOAuthResourceServerFilter.Builder; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleContext; + +/** + * @author Roberto E. Escobar + */ +public class OAuth2ResourceServer { + + private JaxRsApplicationRegistry registry; + + private final Set<String> registeredProviders = new HashSet<String>(); + private final AtomicBoolean wasRegistered = new AtomicBoolean(); + private volatile JaxRsOAuthResourceServerFilter filter; + private volatile List<String> audiences; + private volatile Bundle bundle; + + public void setJaxRsApplicationRegistry(JaxRsApplicationRegistry registry) { + this.registry = registry; + } + + public void start(BundleContext bundleContext, Map<String, Object> props) { + bundle = bundleContext.getBundle(); + update(props); + } + + public void stop() { + if (wasRegistered.getAndSet(false)) { + deregister(registry); + filter = null; + } + } + + public void update(Map<String, Object> props) { + OAuth2ResourceServerConfig config = OAuth2ResourceServerConfig.fromProperties(props); + if (config.isEnabled()) { + if (!wasRegistered.getAndSet(true)) { + initialize(config); + register(registry, bundle); + } + configure(config); + } else { + stop(); + } + } + + private void initialize(OAuth2ResourceServerConfig config) { + audiences = Collections.emptyList(); + + Builder builder = JaxRsOAuthResourceServerFilter.newBuilder() // + .serverKey(config.getResourceServerKey()) // + .serverSecret(config.getResourceServerSecret())// + .serverUri(config.getValidationServerUri()); + + if (config.isCacheTokensAllowed()) { + filter = builder.build(config.getTokenCacheMaxSize(), config.getTokenCacheEvictTimeoutMillis()); + } else { + filter = builder.build(); + } + } + + private void configure(OAuth2ResourceServerConfig config) { + filter.setRealm(config.getRealm()); + filter.setAudienceIsEndpointAddress(config.isAudienceIsEndpointAddress()); + filter.setUseUserSubject(config.isUseUserSubject()); + filter.setCheckFormData(config.isFilterChecksFormDataForToken()); + filter.setAudiences(audiences); + } + + private void register(JaxRsApplicationRegistry registry, Bundle bundle) { + for (Object object : JaxRsOAuth.getOAuthProviders()) { + addProvider(registry, bundle, qualify(object.getClass().getSimpleName()), object); + } + addProvider(registry, bundle, qualify("filter"), filter); + } + + private void addProvider(JaxRsApplicationRegistry registry, Bundle bundle, String name, Object object) { + registeredProviders.add(name); + registry.registerProvider(name, bundle, object); + } + + private void deregister(JaxRsApplicationRegistry registry) { + for (String componentName : registeredProviders) { + registry.deregisterProvider(componentName); + } + registeredProviders.clear(); + } + + private static String qualify(String name) { + return String.format("%s.security.oauth2.client.%s", JaxRsConstants.NAMESPACE, name); + } + +} diff --git a/plugins/org.eclipse.osee.jaxrs.server/src/org/eclipse/osee/jaxrs/server/internal/security/oauth2/rs/OAuth2ResourceServerConfig.java b/plugins/org.eclipse.osee.jaxrs.server/src/org/eclipse/osee/jaxrs/server/internal/security/oauth2/rs/OAuth2ResourceServerConfig.java new file mode 100644 index 00000000000..a3cf8a256e8 --- /dev/null +++ b/plugins/org.eclipse.osee.jaxrs.server/src/org/eclipse/osee/jaxrs/server/internal/security/oauth2/rs/OAuth2ResourceServerConfig.java @@ -0,0 +1,108 @@ +/******************************************************************************* + * Copyright (c) 2014 Boeing. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Boeing - initial API and implementation + *******************************************************************************/ +package org.eclipse.osee.jaxrs.server.internal.security.oauth2.rs; + +import static org.eclipse.osee.jaxrs.server.internal.JaxRsUtils.get; +import static org.eclipse.osee.jaxrs.server.internal.JaxRsUtils.getBoolean; +import static org.eclipse.osee.jaxrs.server.internal.JaxRsUtils.getInt; +import static org.eclipse.osee.jaxrs.server.internal.JaxRsUtils.getLong; +import java.util.Map; + +/** + * @author Roberto E. Escobar + */ +public class OAuth2ResourceServerConfig { + + private static final String NAMESPACE = "jaxrs.oauth2.rs"; + + private static String qualify(String value) { + return String.format("%s.%s", NAMESPACE, value); + } + + public static final String OAUTH2_RS__SERVICE_ENABLED = qualify("enabled"); + public static final String OAUTH2_RS__REALM = qualify("realm"); + public static final String OAUTH2_RS__AUDIENCE_IS_ENDPOINT_ADDRESS = qualify("audience.is.endpoint.address"); + public static final String OAUTH2_RS__FILTER_CHECKS_FORM_DATA = qualify("filter.checks.form.data.for.token"); + public static final String OAUTH2_RS__USE_USER_SUBJECT = qualify("use.user.subject"); + public static final String OAUTH2_RS__RESOURCE_SERVER_KEY = qualify("resource.server.key"); + public static final String OAUTH2_RS__RESOURCE_SERVER_SECRET = qualify("resource.server.secret"); + public static final String OAUTH2_RS__TOKEN_VALIDATION_URI = qualify("token.validation.uri"); + public static final String OAUTH2_RS__IS_CACHE_TOKENS_ALLOWED = qualify("is.cache.tokens.allowed"); + public static final String OAUTH2_RS__TOKEN_CACHE_MAX_SIZE = qualify("token.cache.max.size"); + public static final String OAUTH2_RS__TOKEN_CACHE_EVICT_TIMEOUT_MILLIS = qualify("token.cache.evict.timeout"); + + public static final boolean DEFAULT_OAUTH2_RS__SERVICE_ENABLED = false; + public static final String DEFAULT_OAUTH2_RS__REALM = "OAuth2-Resource-Server-OSEE"; + public static final boolean DEFAULT_OAUTH2_RS__AUDIENCE_IS_ENDPOINT_ADDRESS = false; + public static final boolean DEFAULT_OAUTH2_RS__FILTER_CHECKS_FORM_DATA = false; + public static final boolean DEFAULT_OAUTH2_RS__USE_USER_SUBJECT = false; + public static final boolean DEFAULT_OAUTH2_RS__IS_CACHE_TOKENS_ALLOWED = true; + public static final int DEFAULT_OAUTH2_RS__TOKEN_CACHE_MAX_SIZE = 5000; // 5000 tokens + public static final long DEFAULT_OAUTH2_RS__TOKEN_CACHE_EVICT_TIMEOUT_MILLIS = 4L * 60L * 60L * 1000L; // 4 hours + + public static final long OAUTH2_RS__MAX_TOKEN_CACHE_EVICT_TIMEOUT_MILLIS = 24L * 60L * 60L * 1000L; // one day + + public static OAuth2ResourceServerConfig fromProperties(Map<String, Object> props) { + return new OAuth2ResourceServerConfig(props); + } + + private final Map<String, Object> props; + + private OAuth2ResourceServerConfig(Map<String, Object> props) { + this.props = props; + } + + public boolean isEnabled() { + return getBoolean(props, OAUTH2_RS__SERVICE_ENABLED, DEFAULT_OAUTH2_RS__SERVICE_ENABLED); + } + + public String getRealm() { + return get(props, OAUTH2_RS__REALM, DEFAULT_OAUTH2_RS__REALM); + } + + public boolean isAudienceIsEndpointAddress() { + return getBoolean(props, OAUTH2_RS__AUDIENCE_IS_ENDPOINT_ADDRESS, DEFAULT_OAUTH2_RS__AUDIENCE_IS_ENDPOINT_ADDRESS); + } + + public boolean isFilterChecksFormDataForToken() { + return getBoolean(props, OAUTH2_RS__FILTER_CHECKS_FORM_DATA, DEFAULT_OAUTH2_RS__FILTER_CHECKS_FORM_DATA); + } + + public boolean isUseUserSubject() { + return getBoolean(props, OAUTH2_RS__USE_USER_SUBJECT, DEFAULT_OAUTH2_RS__FILTER_CHECKS_FORM_DATA); + } + + public String getResourceServerKey() { + return get(props, OAUTH2_RS__RESOURCE_SERVER_KEY, null); + } + + public String getResourceServerSecret() { + return get(props, OAUTH2_RS__RESOURCE_SERVER_SECRET, null); + } + + public String getValidationServerUri() { + return get(props, OAUTH2_RS__TOKEN_VALIDATION_URI, null); + } + + public boolean isCacheTokensAllowed() { + return getBoolean(props, OAUTH2_RS__IS_CACHE_TOKENS_ALLOWED, DEFAULT_OAUTH2_RS__IS_CACHE_TOKENS_ALLOWED); + } + + public int getTokenCacheMaxSize() { + return getInt(props, OAUTH2_RS__TOKEN_CACHE_MAX_SIZE, DEFAULT_OAUTH2_RS__TOKEN_CACHE_MAX_SIZE); + } + + public long getTokenCacheEvictTimeoutMillis() { + return getLong(props, OAUTH2_RS__TOKEN_CACHE_EVICT_TIMEOUT_MILLIS, + DEFAULT_OAUTH2_RS__TOKEN_CACHE_EVICT_TIMEOUT_MILLIS); + }; + +} diff --git a/plugins/org.eclipse.osee.jaxrs.server/src/org/eclipse/osee/jaxrs/server/security/JaxRsOAuthResourceServerFilter.java b/plugins/org.eclipse.osee.jaxrs.server/src/org/eclipse/osee/jaxrs/server/security/JaxRsOAuthResourceServerFilter.java new file mode 100644 index 00000000000..1b9953833d0 --- /dev/null +++ b/plugins/org.eclipse.osee.jaxrs.server/src/org/eclipse/osee/jaxrs/server/security/JaxRsOAuthResourceServerFilter.java @@ -0,0 +1,221 @@ +/******************************************************************************* + * Copyright (c) 2014 Boeing. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Boeing - initial API and implementation + *******************************************************************************/ +package org.eclipse.osee.jaxrs.server.security; + +import java.util.Collections; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.TimeUnit; +import javax.servlet.http.HttpServletRequest; +import javax.ws.rs.client.Entity; +import javax.ws.rs.client.WebTarget; +import javax.ws.rs.container.ContainerRequestContext; +import javax.ws.rs.container.ContainerRequestFilter; +import javax.ws.rs.core.Form; +import javax.ws.rs.ext.Provider; +import org.apache.cxf.jaxrs.ext.MessageContext; +import org.apache.cxf.rs.security.oauth2.common.AccessTokenValidation; +import org.apache.cxf.rs.security.oauth2.common.UserSubject; +import org.apache.cxf.rs.security.oauth2.filters.OAuthRequestFilter; +import org.apache.cxf.rs.security.oauth2.provider.AccessTokenValidator; +import org.apache.cxf.rs.security.oauth2.provider.OAuthServiceException; +import org.apache.cxf.rs.security.oauth2.utils.OAuthConstants; +import org.apache.cxf.security.SecurityContext; +import org.eclipse.osee.framework.jdk.core.util.Conditions; +import org.eclipse.osee.framework.jdk.core.util.Lib; +import org.eclipse.osee.jaxrs.client.JaxRsClient; +import org.eclipse.osee.jaxrs.server.internal.security.oauth2.OAuthUtil; +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; + +/** + * Filter used to protect resource server end-points. This filter is used when the resource server is not located in the + * same JVM as the authorization server. When a request is processed, the resource server will contact the authorization + * server and validate the access token provided by the request through the HTTP authorization header. + * + * @author Roberto E. Escobar + */ +@Provider +public class JaxRsOAuthResourceServerFilter implements ContainerRequestFilter { + + private final OAuthRequestFilter delegate; + + private JaxRsOAuthResourceServerFilter(OAuthRequestFilter delegate) { + this.delegate = delegate; + } + + public void setAudienceIsEndpointAddress(boolean audienceIsEndpointAddress) { + delegate.setAudienceIsEndpointAddress(audienceIsEndpointAddress); + } + + public void setCheckFormData(boolean checkFormData) { + delegate.setCheckFormData(checkFormData); + } + + public void setRealm(String realm) { + delegate.setRealm(realm); + } + + public void setUseUserSubject(boolean useUserSubject) { + delegate.setUseUserSubject(useUserSubject); + } + + public void setAudiences(List<String> audiences) { + delegate.setAudiences(audiences); + } + + @Override + public void filter(ContainerRequestContext context) { + delegate.filter(context); + }; + + public static Builder newBuilder() { + return new Builder(); + } + + public static class Builder { + public static final long MAX_TOKEN_CACHE_EVICT_TIMEOUT_MILLIS = 24L * 60L * 60L * 1000L; // one day + + private String resourceServerKey; + private String resourceServerSecret; + private String validationServerUri; + + public JaxRsOAuthResourceServerFilter build() { + ClientAccessTokenValidator validator = newTokenValidator(); + return build(validator); + } + + public JaxRsOAuthResourceServerFilter build(int cacheMaxSize, long cacheEvictTimeoutMillis) { + ClientAccessTokenValidator validator = newCachingTokenValidator(cacheMaxSize, cacheEvictTimeoutMillis); + return build(validator); + } + + private JaxRsOAuthResourceServerFilter build(ClientAccessTokenValidator validator) { + JaxRsClient client = JaxRsClient.newBuilder()// + .username(resourceServerKey)// + .password(resourceServerSecret)// + .build(); + + validator.setClient(client); + validator.setValidationServerUri(validationServerUri); + + OAuth2RequestFilter filter = new OAuth2RequestFilter(); + filter.setTokenValidator(validator); + return new JaxRsOAuthResourceServerFilter(filter); + } + + public Builder serverUri(String validationServerUri) { + this.validationServerUri = validationServerUri; + return this; + } + + public Builder serverKey(String resourceServerKey) { + this.resourceServerKey = resourceServerKey; + return this; + } + + public Builder serverSecret(String resourceServerSecret) { + this.resourceServerSecret = resourceServerSecret; + return this; + } + + private static ClientAccessTokenValidator newTokenValidator() { + return new ClientAccessTokenValidator() { + @Override + public AccessTokenValidation validateAccessToken(MessageContext mc, final String authScheme, final String accessToken) throws OAuthServiceException { + return getRemoteTokenValidation(authScheme, accessToken); + } + }; + } + + private static ClientAccessTokenValidator newCachingTokenValidator(int cacheMaxSize, long cacheEvictTimeoutMillis) { + Conditions.checkExpressionFailOnTrue(cacheMaxSize <= 0, "Token Cache max size must be greater than 0"); + Conditions.checkExpressionFailOnTrue(cacheEvictTimeoutMillis > MAX_TOKEN_CACHE_EVICT_TIMEOUT_MILLIS, + "Token cache evict timeout exceeds max - [%s]", Lib.asTimeString(MAX_TOKEN_CACHE_EVICT_TIMEOUT_MILLIS)); + Conditions.checkExpressionFailOnTrue(cacheEvictTimeoutMillis <= 0, + "Token cache evict timeout must be greater than 0"); + + final Cache<String, AccessTokenValidation> cache = CacheBuilder.newBuilder()// + .maximumSize(cacheMaxSize)// + .expireAfterWrite(cacheEvictTimeoutMillis, TimeUnit.MILLISECONDS)// + .build(); + + return new ClientAccessTokenValidator() { + @Override + public AccessTokenValidation validateAccessToken(MessageContext mc, final String authScheme, final String accessToken) throws OAuthServiceException { + try { + return cache.get(accessToken, new Callable<AccessTokenValidation>() { + @Override + public AccessTokenValidation call() { + return getRemoteTokenValidation(authScheme, accessToken); + } + }); + } catch (Exception ex) { + throw new OAuthServiceException("Error validating access token", ex.getCause()); + } + } + }; + } + + private static class OAuth2RequestFilter extends OAuthRequestFilter { + + private volatile boolean useUserSubject; + + @Override + public void setUseUserSubject(boolean useUserSubject) { + super.setUseUserSubject(useUserSubject); + this.useUserSubject = useUserSubject; + } + + @Override + protected SecurityContext createSecurityContext(HttpServletRequest request, AccessTokenValidation accessTokenV) { + UserSubject resourceOwnerSubject = accessTokenV.getTokenSubject(); + UserSubject clientSubject = accessTokenV.getClientSubject(); + + UserSubject subject; + if (resourceOwnerSubject != null || useUserSubject) { + subject = resourceOwnerSubject; + } else { + subject = clientSubject; + } + return OAuthUtil.newSecurityContext(subject); + } + + } + + private static abstract class ClientAccessTokenValidator implements AccessTokenValidator { + + private JaxRsClient client; + private String validationServerUri; + + public void setClient(JaxRsClient client) { + this.client = client; + } + + public void setValidationServerUri(String validationServerUri) { + this.validationServerUri = validationServerUri; + } + + @Override + public List<String> getSupportedAuthorizationSchemes() { + return Collections.singletonList(OAuthConstants.ALL_AUTH_SCHEMES); + } + + protected AccessTokenValidation getRemoteTokenValidation(String authScheme, String accessToken) { + Form form = new Form(); + form.param(OAuthConstants.AUTHORIZATION_SCHEME_TYPE, authScheme); + form.param(OAuthConstants.AUTHORIZATION_SCHEME_DATA, accessToken); + WebTarget target = client.target(validationServerUri); + return target.request().post(Entity.form(form), AccessTokenValidation.class); + } + } + } +} |