You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@qpid.apache.org by kw...@apache.org on 2016/02/08 18:29:44 UTC
svn commit: r1729215 [1/2] - in /qpid/java/trunk:
broker-codegen/src/main/java/org/apache/qpid/server/model/validation/
broker-core/src/main/java/org/apache/qpid/server/model/
broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/oauth...
Author: kwall
Date: Mon Feb 8 17:29:44 2016
New Revision: 1729215
URL: http://svn.apache.org/viewvc?rev=1729215&view=rev
Log:
QPID-7028, QPID-7029, QPID-7030, QPID-7031, QPID-7045: [Java Broker/Java Client] Add OAUTH2 authentication support for management and messaging
* Includes integration with Google and CloudFoundry OAuth2 backends
Added:
qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/oauth2/
qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/oauth2/IdentityResolverException.java
qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/oauth2/OAuth2AuthenticationProvider.java
qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/oauth2/OAuth2AuthenticationProviderImpl.java
qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/oauth2/OAuth2IdentityResolverService.java
qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/oauth2/OAuth2IdentityResolverServiceFactory.java
qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/oauth2/OAuth2SaslServer.java
qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/oauth2/OAuth2Utils.java
qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/oauth2/cloudfoundry/
qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/oauth2/cloudfoundry/CloudFoundryOAuth2IdentityResolverService.java
qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/oauth2/cloudfoundry/CloudFoundryOAuth2IdentityResolverServiceFactory.java
qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/oauth2/google/
qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/oauth2/google/GoogleOAuth2IdentityResolverService.java
qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/oauth2/google/GoogleOAuth2IdentityResolverServiceFactory.java
qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/oauth2/google/package-info.java
qpid/java/trunk/broker-core/src/test/java/org/apache/qpid/server/security/auth/manager/oauth2/
qpid/java/trunk/broker-core/src/test/java/org/apache/qpid/server/security/auth/manager/oauth2/OAuth2SaslServerTest.java
qpid/java/trunk/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/auth/OAuth2InteractiveAuthenticator.java
qpid/java/trunk/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/auth/OAuth2PreemptiveAuthenticator.java
qpid/java/trunk/client/src/main/java/org/apache/qpid/client/security/oauth2/
qpid/java/trunk/client/src/main/java/org/apache/qpid/client/security/oauth2/OAuth2AccessTokenCallbackHandler.java
qpid/java/trunk/client/src/main/java/org/apache/qpid/client/security/oauth2/OAuth2SaslClient.java
qpid/java/trunk/client/src/main/java/org/apache/qpid/client/security/oauth2/OAuth2SaslClientFactory.java
qpid/java/trunk/client/src/test/java/org/apache/qpid/client/security/oauth2/
qpid/java/trunk/client/src/test/java/org/apache/qpid/client/security/oauth2/OAuth2AccessTokenCallbackHandlerTest.java
qpid/java/trunk/client/src/test/java/org/apache/qpid/client/security/oauth2/OAuth2SaslClientTest.java
Modified:
qpid/java/trunk/broker-codegen/src/main/java/org/apache/qpid/server/model/validation/AttributeAnnotationValidator.java
qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/model/AttributeValueConverter.java
qpid/java/trunk/client/src/main/java/org/apache/qpid/client/security/CallbackHandlerRegistry.properties
qpid/java/trunk/client/src/main/java/org/apache/qpid/client/security/DynamicSaslRegistrar.properties
Modified: qpid/java/trunk/broker-codegen/src/main/java/org/apache/qpid/server/model/validation/AttributeAnnotationValidator.java
URL: http://svn.apache.org/viewvc/qpid/java/trunk/broker-codegen/src/main/java/org/apache/qpid/server/model/validation/AttributeAnnotationValidator.java?rev=1729215&r1=1729214&r2=1729215&view=diff
==============================================================================
--- qpid/java/trunk/broker-codegen/src/main/java/org/apache/qpid/server/model/validation/AttributeAnnotationValidator.java (original)
+++ qpid/java/trunk/broker-codegen/src/main/java/org/apache/qpid/server/model/validation/AttributeAnnotationValidator.java Mon Feb 8 17:29:44 2016
@@ -317,6 +317,10 @@ public class AttributeAnnotationValidato
return true;
}
+ if(typeUtils.isSameType(type,elementUtils.getTypeElement("java.net.URI").asType()))
+ {
+ return true;
+ }
if(typeUtils.isSameType(type,elementUtils.getTypeElement("java.security.cert.Certificate").asType()))
{
Modified: qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/model/AttributeValueConverter.java
URL: http://svn.apache.org/viewvc/qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/model/AttributeValueConverter.java?rev=1729215&r1=1729214&r2=1729215&view=diff
==============================================================================
--- qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/model/AttributeValueConverter.java (original)
+++ qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/model/AttributeValueConverter.java Mon Feb 8 17:29:44 2016
@@ -29,6 +29,7 @@ import java.lang.reflect.ParameterizedTy
import java.lang.reflect.Proxy;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
+import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
@@ -105,6 +106,30 @@ abstract class AttributeValueConverter<T
}
};
+ static final AttributeValueConverter<URI> URI_CONVERTER = new AttributeValueConverter<URI>()
+ {
+ @Override
+ URI convert(final Object value, final ConfiguredObject object)
+ {
+ if(value instanceof URI)
+ {
+ return (URI) value;
+ }
+ else if(value instanceof String)
+ {
+ return URI.create(AbstractConfiguredObject.interpolate(object, (String) value));
+ }
+ else if(value == null)
+ {
+ return null;
+ }
+ else
+ {
+ throw new IllegalArgumentException("Cannot convert type " + value.getClass() + " to a URI");
+ }
+ }
+ };
+
static final AttributeValueConverter<byte[]> BINARY_CONVERTER = new AttributeValueConverter<byte[]>()
{
@Override
@@ -531,6 +556,10 @@ abstract class AttributeValueConverter<T
{
return (AttributeValueConverter<X>) UUID_CONVERTER;
}
+ else if(type == URI.class)
+ {
+ return (AttributeValueConverter<X>) URI_CONVERTER;
+ }
else if(type == byte[].class)
{
return (AttributeValueConverter<X>) BINARY_CONVERTER;
Added: qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/oauth2/IdentityResolverException.java
URL: http://svn.apache.org/viewvc/qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/oauth2/IdentityResolverException.java?rev=1729215&view=auto
==============================================================================
--- qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/oauth2/IdentityResolverException.java (added)
+++ qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/oauth2/IdentityResolverException.java Mon Feb 8 17:29:44 2016
@@ -0,0 +1,34 @@
+/*
+ *
+ * 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.qpid.server.security.auth.manager.oauth2;
+
+public class IdentityResolverException extends Exception
+{
+ public IdentityResolverException(final String message)
+ {
+ super(message);
+ }
+
+ public IdentityResolverException(final String message, final Throwable cause)
+ {
+ super(message, cause);
+ }
+}
Added: qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/oauth2/OAuth2AuthenticationProvider.java
URL: http://svn.apache.org/viewvc/qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/oauth2/OAuth2AuthenticationProvider.java?rev=1729215&view=auto
==============================================================================
--- qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/oauth2/OAuth2AuthenticationProvider.java (added)
+++ qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/oauth2/OAuth2AuthenticationProvider.java Mon Feb 8 17:29:44 2016
@@ -0,0 +1,67 @@
+/*
+ * 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.qpid.server.security.auth.manager.oauth2;
+
+import java.net.URI;
+import java.util.List;
+
+import org.apache.qpid.server.model.AuthenticationProvider;
+import org.apache.qpid.server.model.ManagedAttribute;
+import org.apache.qpid.server.model.ManagedObject;
+import org.apache.qpid.server.model.TrustStore;
+import org.apache.qpid.server.security.auth.AuthenticationResult;
+
+@ManagedObject( category = false, type = "OAuth2" )
+public interface OAuth2AuthenticationProvider<T extends OAuth2AuthenticationProvider<T>> extends AuthenticationProvider<T>
+{
+ @ManagedAttribute( description = "Redirect URI to obtain authorization code grant", mandatory = true )
+ URI getAuthorizationEndpointURI();
+
+ @ManagedAttribute( description = "Token endpoint URI", mandatory = true )
+ URI getTokenEndpointURI();
+
+ @ManagedAttribute( description = "Whether to use basic authentication when accessing the token endpoint", defaultValue = "false" )
+ boolean getTokenEndpointNeedsAuth();
+
+ @ManagedAttribute( description = "Identity resolver endpoint URI", mandatory = true )
+ URI getIdentityResolverEndpointURI();
+
+ @ManagedAttribute( description = "The type of the IdentityResolverFactory", mandatory = true )
+ String getIdentityResolverFactoryType();
+
+ @ManagedAttribute( description = "Client ID to identify qpid to the OAuth endpoints", mandatory = true )
+ String getClientId();
+
+ @ManagedAttribute( description = "Client secret to identify qpid to the OAuth endpoints", mandatory = true, secure = true )
+ String getClientSecret();
+
+ @ManagedAttribute( description = "The OAuth access token scope passed to the authorization endpoint" )
+ String getScope();
+
+ @ManagedAttribute( description = "TrustStore to use when contacting OAuth endpoints" )
+ TrustStore getTrustStore();
+
+ @ManagedAttribute( defaultValue = "[ \"XOAUTH2\" ]")
+ List<String> getSecureOnlyMechanisms();
+
+ AuthenticationResult authenticateViaAuthorizationCode(String authorizationCode, final String redirectUri);
+
+ AuthenticationResult authenticateViaAccessToken(String accessToken);
+}
Added: qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/oauth2/OAuth2AuthenticationProviderImpl.java
URL: http://svn.apache.org/viewvc/qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/oauth2/OAuth2AuthenticationProviderImpl.java?rev=1729215&view=auto
==============================================================================
--- qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/oauth2/OAuth2AuthenticationProviderImpl.java (added)
+++ qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/oauth2/OAuth2AuthenticationProviderImpl.java Mon Feb 8 17:29:44 2016
@@ -0,0 +1,314 @@
+/*
+ * 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.qpid.server.security.auth.manager.oauth2;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URI;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.security.Principal;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.net.ssl.HttpsURLConnection;
+import javax.security.sasl.SaslException;
+import javax.security.sasl.SaslServer;
+import javax.xml.bind.DatatypeConverter;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.apache.qpid.server.model.Broker;
+import org.apache.qpid.server.model.ManagedAttributeField;
+import org.apache.qpid.server.model.ManagedObjectFactoryConstructor;
+import org.apache.qpid.server.model.TrustStore;
+import org.apache.qpid.server.security.auth.AuthenticatedPrincipal;
+import org.apache.qpid.server.security.auth.AuthenticationResult;
+import org.apache.qpid.server.security.auth.manager.AbstractAuthenticationManager;
+
+public class OAuth2AuthenticationProviderImpl
+ extends AbstractAuthenticationManager<OAuth2AuthenticationProviderImpl>
+ implements OAuth2AuthenticationProvider<OAuth2AuthenticationProviderImpl>
+{
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(OAuth2AuthenticationProviderImpl.class);
+ private static final String UTF8 = StandardCharsets.UTF_8.name();
+
+ private final ObjectMapper _objectMapper = new ObjectMapper();
+
+ @ManagedAttributeField
+ private URI _authorizationEndpointURI;
+
+ @ManagedAttributeField
+ private URI _tokenEndpointURI;
+
+ @ManagedAttributeField
+ private URI _identityResolverEndpointURI;
+
+ @ManagedAttributeField
+ private boolean _tokenEndpointNeedsAuth;
+
+ @ManagedAttributeField
+ private String _clientId;
+
+ @ManagedAttributeField
+ private String _clientSecret;
+
+ @ManagedAttributeField
+ private TrustStore _trustStore;
+
+ @ManagedAttributeField
+ private String _scope;
+
+ @ManagedAttributeField
+ private String _identityResolverFactoryType;
+
+ private OAuth2IdentityResolverService _identityResolverService;
+
+ @ManagedObjectFactoryConstructor
+ protected OAuth2AuthenticationProviderImpl(final Map<String, Object> attributes,
+ final Broker<?> broker)
+ {
+ super(attributes, broker);
+ }
+
+ @Override
+ protected void onOpen()
+ {
+ super.onOpen();
+ String type = getIdentityResolverFactoryType();
+ OAuth2IdentityResolverServiceFactory factory = OAuth2IdentityResolverServiceFactory.FACTORIES.get(type);
+ _identityResolverService = factory.createIdentityResolverService(this);
+ }
+
+ @Override
+ public List<String> getMechanisms()
+ {
+ return Collections.singletonList(OAuth2SaslServer.MECHANISM);
+ }
+
+ @Override
+ public SaslServer createSaslServer(final String mechanism,
+ final String localFQDN,
+ final Principal externalPrincipal)
+ throws SaslException
+ {
+ if(OAuth2SaslServer.MECHANISM.equals(mechanism))
+ {
+ return new OAuth2SaslServer();
+ }
+ else
+ {
+ throw new SaslException("Unknown mechanism: " + mechanism);
+ }
+ }
+
+ @Override
+ public AuthenticationResult authenticate(final SaslServer server, final byte[] response)
+ {
+ try
+ {
+ // Process response from the client
+ byte[] challenge = server.evaluateResponse(response != null ? response : new byte[0]);
+
+ if (server.isComplete())
+ {
+ String accessToken = (String) server.getNegotiatedProperty(OAuth2SaslServer.ACCESS_TOKEN_PROPERTY);
+ return authenticateViaAccessToken(accessToken);
+ }
+ else
+ {
+ return new AuthenticationResult(challenge, AuthenticationResult.AuthenticationStatus.CONTINUE);
+ }
+ }
+ catch (SaslException e)
+ {
+ return new AuthenticationResult(AuthenticationResult.AuthenticationStatus.ERROR, e);
+ }
+ }
+
+ @Override
+ public AuthenticationResult authenticateViaAuthorizationCode(final String authorizationCode, final String redirectUri)
+ {
+ URL tokenEndpoint;
+ HttpsURLConnection connection;
+ byte[] body;
+ try
+ {
+ tokenEndpoint = getTokenEndpointURI().toURL();
+
+ LOGGER.debug("About to call token endpoint '{}'", tokenEndpoint);
+
+ connection = (HttpsURLConnection) tokenEndpoint.openConnection();
+
+ if (getTrustStore() != null)
+ {
+ OAuth2Utils.setTrustedCertificates(connection, getTrustStore());
+ }
+
+ connection.setDoOutput(true); // makes sure to use POST
+ connection.setRequestProperty("Accept-Charset", UTF8);
+ connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded;charset=" + UTF8);
+ connection.setRequestProperty("Accept", "application/json");
+
+ if (getTokenEndpointNeedsAuth())
+ {
+ String encoded = DatatypeConverter.printBase64Binary((getClientId() + ":" + getClientSecret()).getBytes());
+ connection.setRequestProperty("Authorization", "Basic " + encoded);
+ }
+
+ Map<String, String> requestBody = new HashMap<>();
+ requestBody.put("code", authorizationCode);
+ requestBody.put("client_id", getClientId());
+ requestBody.put("client_secret", getClientSecret());
+ requestBody.put("redirect_uri", redirectUri);
+ requestBody.put("grant_type", "authorization_code");
+ requestBody.put("response_type", "token");
+ body = OAuth2Utils.buildRequestQuery(requestBody).getBytes(UTF8);
+ connection.connect();
+ }
+ catch (IOException e)
+ {
+ return new AuthenticationResult(AuthenticationResult.AuthenticationStatus.ERROR, e);
+ }
+
+ try (OutputStream output = connection.getOutputStream())
+ {
+ output.write(body);
+ output.close();
+
+ try (InputStream input = connection.getInputStream())
+ {
+ final int responseCode = connection.getResponseCode();
+ LOGGER.debug("Call to token endpoint '{}' complete, response code : {}", tokenEndpoint, responseCode);
+
+ Map<String, Object> responseMap = _objectMapper.readValue(input, Map.class);
+ if (responseCode != 200)
+ {
+ IllegalStateException e = new IllegalStateException(String.format("Token endpoint failed, response code %d, error '%s', description '%s'",
+ responseCode,
+ responseMap.get("error"),
+ responseMap.get("error_description")));
+ return new AuthenticationResult(AuthenticationResult.AuthenticationStatus.ERROR, e);
+ }
+ return getAuthenticationResult(responseMap);
+ }
+ catch (JsonProcessingException e)
+ {
+ IllegalStateException ise = new IllegalStateException(String.format("Token endpoint '%s' did not return json",
+ tokenEndpoint),
+ e);
+ return new AuthenticationResult(AuthenticationResult.AuthenticationStatus.ERROR, ise);
+ }
+ }
+ catch (IOException | IdentityResolverException e)
+ {
+ return new AuthenticationResult(AuthenticationResult.AuthenticationStatus.ERROR, e);
+ }
+ }
+
+ @Override
+ public AuthenticationResult authenticateViaAccessToken(String accessToken)
+ {
+ try
+ {
+ return new AuthenticationResult(new AuthenticatedPrincipal(_identityResolverService.getUserPrincipal(accessToken)));
+ }
+ catch (IOException | IdentityResolverException e)
+ {
+ return new AuthenticationResult(AuthenticationResult.AuthenticationStatus.ERROR, e);
+ }
+ }
+
+ @Override
+ public URI getAuthorizationEndpointURI()
+ {
+ return _authorizationEndpointURI;
+ }
+
+ @Override
+ public URI getTokenEndpointURI()
+ {
+ return _tokenEndpointURI;
+ }
+
+ @Override
+ public URI getIdentityResolverEndpointURI()
+ {
+ return _identityResolverEndpointURI;
+ }
+
+ @Override
+ public boolean getTokenEndpointNeedsAuth()
+ {
+ return _tokenEndpointNeedsAuth;
+ }
+
+ @Override
+ public String getIdentityResolverFactoryType()
+ {
+ return _identityResolverFactoryType;
+ }
+
+ @Override
+ public String getClientId()
+ {
+ return _clientId;
+ }
+
+ @Override
+ public String getClientSecret()
+ {
+ return _clientSecret;
+ }
+
+ @Override
+ public TrustStore getTrustStore()
+ {
+ return _trustStore;
+ }
+
+ @Override
+ public String getScope()
+ {
+ return _scope;
+ }
+
+ private AuthenticationResult getAuthenticationResult(Map<String, Object> tokenEndpointResponse)
+ throws IOException, IdentityResolverException
+ {
+ final Object accessTokenObject = tokenEndpointResponse.get("access_token");
+ if (accessTokenObject == null)
+ {
+ final IllegalStateException e = new IllegalStateException("Token endpoint response did not include 'access_token'");
+ return new AuthenticationResult(AuthenticationResult.AuthenticationStatus.ERROR, e);
+ }
+ String accessToken = String.valueOf(accessTokenObject);
+
+ return new AuthenticationResult(new AuthenticatedPrincipal(_identityResolverService.getUserPrincipal(accessToken)));
+ }
+
+}
Added: qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/oauth2/OAuth2IdentityResolverService.java
URL: http://svn.apache.org/viewvc/qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/oauth2/OAuth2IdentityResolverService.java?rev=1729215&view=auto
==============================================================================
--- qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/oauth2/OAuth2IdentityResolverService.java (added)
+++ qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/oauth2/OAuth2IdentityResolverService.java Mon Feb 8 17:29:44 2016
@@ -0,0 +1,30 @@
+/*
+ *
+ * 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.qpid.server.security.auth.manager.oauth2;
+
+import java.io.IOException;
+import java.security.Principal;
+
+public interface OAuth2IdentityResolverService
+{
+ Principal getUserPrincipal(String accessToken) throws IOException, IdentityResolverException;
+}
Added: qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/oauth2/OAuth2IdentityResolverServiceFactory.java
URL: http://svn.apache.org/viewvc/qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/oauth2/OAuth2IdentityResolverServiceFactory.java?rev=1729215&view=auto
==============================================================================
--- qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/oauth2/OAuth2IdentityResolverServiceFactory.java (added)
+++ qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/oauth2/OAuth2IdentityResolverServiceFactory.java Mon Feb 8 17:29:44 2016
@@ -0,0 +1,54 @@
+/*
+ *
+ * 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.qpid.server.security.auth.manager.oauth2;
+
+import java.net.URI;
+
+import org.apache.qpid.server.plugin.Pluggable;
+import org.apache.qpid.server.plugin.PluggableService;
+import org.apache.qpid.server.plugin.QpidServiceLoader;
+
+@PluggableService
+public interface OAuth2IdentityResolverServiceFactory extends Pluggable
+{
+ OAuth2IdentityResolverService createIdentityResolverService(OAuth2AuthenticationProvider authenticationProvider);
+
+ final class FACTORIES
+ {
+ private FACTORIES()
+ {
+ }
+
+ public static OAuth2IdentityResolverServiceFactory get(String type)
+ {
+ QpidServiceLoader qpidServiceLoader = new QpidServiceLoader();
+ Iterable<OAuth2IdentityResolverServiceFactory> factories = qpidServiceLoader.atLeastOneInstanceOf(OAuth2IdentityResolverServiceFactory.class);
+ for(OAuth2IdentityResolverServiceFactory factory : factories)
+ {
+ if(factory.getType().equals(type))
+ {
+ return factory;
+ }
+ }
+ return null;
+ }
+ }
+}
Added: qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/oauth2/OAuth2SaslServer.java
URL: http://svn.apache.org/viewvc/qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/oauth2/OAuth2SaslServer.java?rev=1729215&view=auto
==============================================================================
--- qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/oauth2/OAuth2SaslServer.java (added)
+++ qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/oauth2/OAuth2SaslServer.java Mon Feb 8 17:29:44 2016
@@ -0,0 +1,140 @@
+/*
+ *
+ * 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.qpid.server.security.auth.manager.oauth2;
+
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.security.sasl.SaslException;
+import javax.security.sasl.SaslServer;
+
+/**
+ * https://developers.google.com/gmail/xoauth2_protocol
+ */
+class OAuth2SaslServer implements SaslServer
+{
+ public static final String MECHANISM = "XOAUTH2";
+ public static final String ACCESS_TOKEN_PROPERTY = "accessToken";
+
+ private static final String BEARER_PREFIX = "Bearer ";
+
+ private String _accessToken;
+ private boolean _isComplete;
+
+ @Override
+ public String getMechanismName()
+ {
+ return MECHANISM;
+ }
+
+ @Override
+ public byte[] evaluateResponse(final byte[] response) throws SaslException
+ {
+ Map<String, String> responsePairs = splitResponse(response);
+
+ String auth = responsePairs.get("auth");
+ if (auth != null)
+ {
+ if (auth.startsWith(BEARER_PREFIX))
+ {
+ _accessToken = auth.substring(BEARER_PREFIX.length());
+ _isComplete = true;
+ }
+ else
+ {
+ throw new SaslException("The 'auth' part of response does not not begin with the expected prefix");
+ }
+ }
+ else
+ {
+ throw new SaslException("The mandatory 'auth' part of the response was absent.");
+ }
+
+ return new byte[0];
+ }
+
+ @Override
+ public boolean isComplete()
+ {
+ return _isComplete;
+ }
+
+ @Override
+ public String getAuthorizationID()
+ {
+ return null;
+ }
+
+ @Override
+ public Object getNegotiatedProperty(final String propName)
+ {
+ if (!_isComplete)
+ {
+ throw new IllegalStateException("authentication exchange has not completed");
+ }
+ if (ACCESS_TOKEN_PROPERTY.equals(propName))
+ {
+ return _accessToken;
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ @Override
+ public byte[] unwrap(final byte[] incoming, final int offset, final int len) throws SaslException
+ {
+ throw new SaslException("");
+ }
+
+ @Override
+ public byte[] wrap(final byte[] outgoing, final int offset, final int len) throws SaslException
+ {
+ throw new SaslException("");
+ }
+
+ @Override
+ public void dispose() throws SaslException
+ {
+ _accessToken = null;
+ }
+
+ private Map<String, String> splitResponse(final byte[] response)
+ {
+ String[] splitResponse = new String(response, StandardCharsets.US_ASCII).split("\1");
+ Map<String, String> responseItems = new HashMap<>(splitResponse.length);
+ for(String nameValue : splitResponse)
+ {
+ if (nameValue.length() > 0)
+ {
+ String[] nameValueSplit = nameValue.split("=", 2);
+ if (nameValueSplit.length == 2)
+ {
+ responseItems.put(nameValueSplit[0], nameValueSplit[1]);
+ }
+ }
+ }
+ return responseItems;
+ }
+}
Added: qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/oauth2/OAuth2Utils.java
URL: http://svn.apache.org/viewvc/qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/oauth2/OAuth2Utils.java?rev=1729215&view=auto
==============================================================================
--- qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/oauth2/OAuth2Utils.java (added)
+++ qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/oauth2/OAuth2Utils.java Mon Feb 8 17:29:44 2016
@@ -0,0 +1,123 @@
+/*
+ *
+ * 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.qpid.server.security.auth.manager.oauth2;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.security.GeneralSecurityException;
+import java.security.cert.Certificate;
+import java.security.cert.X509Certificate;
+import java.util.Iterator;
+import java.util.Map;
+
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLPeerUnverifiedException;
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.SSLSocketFactory;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.apache.qpid.server.model.TrustStore;
+import org.apache.qpid.server.util.ServerScopedRuntimeException;
+import org.apache.qpid.transport.TransportException;
+import org.apache.qpid.transport.network.security.ssl.SSLUtil;
+
+public class OAuth2Utils
+{
+ private static final Logger LOGGER = LoggerFactory.getLogger(OAuth2Utils.class);
+
+ public static void setTrustedCertificates(final HttpsURLConnection connection,
+ final TrustStore trustStore)
+ {
+ final SSLContext sslContext;
+ try
+ {
+ sslContext = SSLContext.getInstance("TLS");
+ sslContext.init(null, trustStore.getTrustManagers(), null);
+ }
+ catch (GeneralSecurityException e)
+ {
+ throw new ServerScopedRuntimeException("Cannot initialise TLS", e);
+ }
+
+ final SSLSocketFactory socketFactory = sslContext.getSocketFactory();
+ connection.setSSLSocketFactory(socketFactory);
+ connection.setHostnameVerifier(new HostnameVerifier()
+ {
+ @Override
+ public boolean verify(final String hostname, final SSLSession sslSession)
+ {
+ try
+ {
+ final Certificate cert = sslSession.getPeerCertificates()[0];
+ if (cert instanceof X509Certificate)
+ {
+ final X509Certificate x509Certificate = (X509Certificate) cert;
+ SSLUtil.verifyHostname(hostname, x509Certificate);
+ return true;
+ }
+ else
+ {
+ LOGGER.warn("Cannot verify peer's hostname as peer does not present a X509Certificate. "
+ + "Presented certificate : {}", cert);
+ }
+ }
+ catch (SSLPeerUnverifiedException | TransportException e)
+ {
+ LOGGER.warn("Failed to verify peer's hostname (connecting to host {})", hostname, e);
+ }
+
+ return false;
+ }
+ });
+ // TODO respect the tls protocols/cipher suite settings
+ }
+
+ public static String buildRequestQuery(final Map<String, String> requestBodyParameters)
+ {
+ try
+ {
+ final String charset = StandardCharsets.UTF_8.name();
+ StringBuilder bodyBuilder = new StringBuilder();
+ Iterator<Map.Entry<String, String>> iterator = requestBodyParameters.entrySet().iterator();
+ while (iterator.hasNext())
+ {
+ Map.Entry<String, String> entry = iterator.next();
+ bodyBuilder.append(URLEncoder.encode(entry.getKey(), charset));
+ bodyBuilder.append("=");
+ bodyBuilder.append(URLEncoder.encode(entry.getValue(), charset));
+ if (iterator.hasNext())
+ {
+ bodyBuilder.append("&");
+ }
+ }
+ return bodyBuilder.toString();
+ }
+ catch (UnsupportedEncodingException e)
+ {
+ throw new ServerScopedRuntimeException("Failed to encode as UTF-8", e);
+ }
+ }
+}
Added: qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/oauth2/cloudfoundry/CloudFoundryOAuth2IdentityResolverService.java
URL: http://svn.apache.org/viewvc/qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/oauth2/cloudfoundry/CloudFoundryOAuth2IdentityResolverService.java?rev=1729215&view=auto
==============================================================================
--- qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/oauth2/cloudfoundry/CloudFoundryOAuth2IdentityResolverService.java (added)
+++ qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/oauth2/cloudfoundry/CloudFoundryOAuth2IdentityResolverService.java Mon Feb 8 17:29:44 2016
@@ -0,0 +1,132 @@
+/*
+ *
+ * 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.qpid.server.security.auth.manager.oauth2.cloudfoundry;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URI;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.security.Principal;
+import java.util.Collections;
+import java.util.Map;
+
+import javax.net.ssl.HttpsURLConnection;
+import javax.xml.bind.DatatypeConverter;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.apache.qpid.server.model.TrustStore;
+import org.apache.qpid.server.security.auth.UsernamePrincipal;
+import org.apache.qpid.server.security.auth.manager.oauth2.IdentityResolverException;
+import org.apache.qpid.server.security.auth.manager.oauth2.OAuth2AuthenticationProvider;
+import org.apache.qpid.server.security.auth.manager.oauth2.OAuth2IdentityResolverService;
+import org.apache.qpid.server.security.auth.manager.oauth2.OAuth2Utils;
+
+public class CloudFoundryOAuth2IdentityResolverService implements OAuth2IdentityResolverService
+{
+ private static final Logger LOGGER = LoggerFactory.getLogger(CloudFoundryOAuth2IdentityResolverService.class);
+ private static final String UTF8 = StandardCharsets.UTF_8.name();
+
+ private final OAuth2AuthenticationProvider _authenticationProvider;
+ private final URI _checkTokenEndpointURI;
+ private final TrustStore _trustStore;
+ private final String _clientId;
+ private final String _clientSecret;
+ private final ObjectMapper _objectMapper = new ObjectMapper();
+
+ public CloudFoundryOAuth2IdentityResolverService(final OAuth2AuthenticationProvider authenticationProvider)
+ {
+ _authenticationProvider = authenticationProvider;
+ _checkTokenEndpointURI = _authenticationProvider.getIdentityResolverEndpointURI();
+ _trustStore = _authenticationProvider.getTrustStore();
+ _clientId = _authenticationProvider.getClientId();
+ _clientSecret = _authenticationProvider.getClientSecret();
+ }
+
+ @Override
+ public Principal getUserPrincipal(final String accessToken) throws IOException, IdentityResolverException
+ {
+ URL checkTokenEndpoint;
+ HttpsURLConnection connection;
+ checkTokenEndpoint = _checkTokenEndpointURI.toURL();
+
+ LOGGER.debug("About to call identity service '{}'", checkTokenEndpoint);
+
+ connection = (HttpsURLConnection) checkTokenEndpoint.openConnection();
+ if (_trustStore != null)
+ {
+ OAuth2Utils.setTrustedCertificates(connection, _trustStore);
+ }
+
+ connection.setDoOutput(true); // makes sure to use POST
+ connection.setRequestProperty("Accept-Charset", UTF8);
+ connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded;charset=" + UTF8);
+ connection.setRequestProperty("Accept", "application/json");
+ String encoded = DatatypeConverter.printBase64Binary((_clientId + ":" + _clientSecret).getBytes());
+ connection.setRequestProperty("Authorization", "Basic " + encoded);
+
+ final Map<String,String> requestParameters = Collections.singletonMap("token", accessToken);
+
+ connection.connect();
+
+ try (OutputStream output = connection.getOutputStream())
+ {
+ output.write(OAuth2Utils.buildRequestQuery(requestParameters).getBytes(UTF8));
+ output.close();
+
+ try (InputStream input = connection.getInputStream())
+ {
+ int responseCode = connection.getResponseCode();
+ LOGGER.debug("Call to identity service '{}' complete, response code : {}", checkTokenEndpoint, responseCode);
+
+ Map<String, String> responseMap = null;
+ try
+ {
+ responseMap = _objectMapper.readValue(input, Map.class);
+ }
+ catch (JsonProcessingException e)
+ {
+ throw new IOException(String.format("Identity resolver '%s' did not return json", checkTokenEndpoint), e);
+ }
+ if (responseCode != 200)
+ {
+ throw new IdentityResolverException(String.format("Identity resolver '%s' failed, response code %d, error '%s', description '%s'",
+ checkTokenEndpoint,
+ responseCode,
+ responseMap.get("error"),
+ responseMap.get("error_description")));
+ }
+ final String userName = responseMap.get("user_name");
+ if (userName == null)
+ {
+ throw new IdentityResolverException(String.format("Identity resolver '%s' failed, response did not include 'user_name'",
+ checkTokenEndpoint));
+ }
+ return new UsernamePrincipal(userName);
+ }
+ }
+ }
+}
Added: qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/oauth2/cloudfoundry/CloudFoundryOAuth2IdentityResolverServiceFactory.java
URL: http://svn.apache.org/viewvc/qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/oauth2/cloudfoundry/CloudFoundryOAuth2IdentityResolverServiceFactory.java?rev=1729215&view=auto
==============================================================================
--- qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/oauth2/cloudfoundry/CloudFoundryOAuth2IdentityResolverServiceFactory.java (added)
+++ qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/oauth2/cloudfoundry/CloudFoundryOAuth2IdentityResolverServiceFactory.java Mon Feb 8 17:29:44 2016
@@ -0,0 +1,44 @@
+/*
+ *
+ * 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.qpid.server.security.auth.manager.oauth2.cloudfoundry;
+
+import org.apache.qpid.server.plugin.PluggableService;
+import org.apache.qpid.server.security.auth.manager.oauth2.OAuth2AuthenticationProvider;
+import org.apache.qpid.server.security.auth.manager.oauth2.OAuth2IdentityResolverService;
+import org.apache.qpid.server.security.auth.manager.oauth2.OAuth2IdentityResolverServiceFactory;
+
+@PluggableService
+public class CloudFoundryOAuth2IdentityResolverServiceFactory implements OAuth2IdentityResolverServiceFactory
+{
+ public static final String TYPE = "CloudFoundryIdentityResolver";
+
+ @Override
+ public OAuth2IdentityResolverService createIdentityResolverService(final OAuth2AuthenticationProvider authenticationProvider)
+ {
+ return new CloudFoundryOAuth2IdentityResolverService(authenticationProvider);
+ }
+
+ @Override
+ public String getType()
+ {
+ return TYPE;
+ }
+}
Added: qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/oauth2/google/GoogleOAuth2IdentityResolverService.java
URL: http://svn.apache.org/viewvc/qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/oauth2/google/GoogleOAuth2IdentityResolverService.java?rev=1729215&view=auto
==============================================================================
--- qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/oauth2/google/GoogleOAuth2IdentityResolverService.java (added)
+++ qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/oauth2/google/GoogleOAuth2IdentityResolverService.java Mon Feb 8 17:29:44 2016
@@ -0,0 +1,130 @@
+/*
+ *
+ * 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.qpid.server.security.auth.manager.oauth2.google;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.nio.charset.StandardCharsets;
+import java.security.Principal;
+import java.util.Map;
+
+import javax.net.ssl.HttpsURLConnection;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.common.collect.Sets;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.apache.qpid.server.model.TrustStore;
+import org.apache.qpid.server.security.auth.UsernamePrincipal;
+import org.apache.qpid.server.security.auth.manager.oauth2.IdentityResolverException;
+import org.apache.qpid.server.security.auth.manager.oauth2.OAuth2AuthenticationProvider;
+import org.apache.qpid.server.security.auth.manager.oauth2.OAuth2IdentityResolverService;
+import org.apache.qpid.server.security.auth.manager.oauth2.OAuth2Utils;
+
+/**
+ * An identity resolver that calls Google's userinfo endpoint https://www.googleapis.com/oauth2/v3/userinfo.
+ *
+ * It requires that the authentication request includes the scope 'profile' in order that 'sub'
+ * (the user identifier) appears in userinfo's response.
+ *
+ * For endpoint is documented:
+ *
+ * https://developers.google.com/identity/protocols/OpenIDConnect
+ */
+public class GoogleOAuth2IdentityResolverService implements OAuth2IdentityResolverService
+{
+ private static final Logger LOGGER = LoggerFactory.getLogger(GoogleOAuth2IdentityResolverService.class);
+ private static final String UTF8 = StandardCharsets.UTF_8.name();
+
+ private final OAuth2AuthenticationProvider _authenticationProvider;
+ private final URI _userInfoEndpoint;
+ private final TrustStore _trustStore;
+ private final ObjectMapper _objectMapper = new ObjectMapper();
+
+ public GoogleOAuth2IdentityResolverService(final OAuth2AuthenticationProvider authenticationProvider)
+ {
+ _authenticationProvider = authenticationProvider;
+ _userInfoEndpoint = _authenticationProvider.getIdentityResolverEndpointURI();
+ _trustStore = _authenticationProvider.getTrustStore();
+
+ if (!Sets.newHashSet(_authenticationProvider.getScope().split("\\s")).contains("profile"))
+ {
+ throw new IllegalArgumentException("This identity resolver requires that scope 'profile' is included in"
+ + " the authentication request.");
+ }
+ }
+
+ @Override
+ public Principal getUserPrincipal(String accessToken) throws IOException, IdentityResolverException
+ {
+ LOGGER.debug("About to call identity service '{}'", _userInfoEndpoint);
+
+ HttpsURLConnection connection = (HttpsURLConnection) _userInfoEndpoint.toURL().openConnection();
+ if (_trustStore != null)
+ {
+ OAuth2Utils.setTrustedCertificates(connection, _trustStore);
+ }
+
+ connection.setRequestProperty("Accept-Charset", UTF8);
+ connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded;charset=" + UTF8);
+ connection.setRequestProperty("Accept", "application/json");
+ connection.setRequestProperty("Authorization", "Bearer " + accessToken);
+
+ connection.connect();
+
+ try (InputStream input = connection.getInputStream())
+ {
+ int responseCode = connection.getResponseCode();
+ LOGGER.debug("Call to identity service '{}' complete, response code : {}",
+ _userInfoEndpoint, responseCode);
+
+ Map<String, String> responseMap;
+ try
+ {
+ responseMap = _objectMapper.readValue(input, Map.class);
+ }
+ catch (JsonProcessingException e)
+ {
+ throw new IOException(String.format("Identity resolver '%s' did not return json",
+ _userInfoEndpoint), e);
+ }
+ if (responseCode != 200)
+ {
+ throw new IdentityResolverException(String.format(
+ "Identity resolver '%s' failed, response code %d",
+ _userInfoEndpoint, responseCode));
+ }
+
+ final String googleId = responseMap.get("sub");
+ if (googleId == null)
+ {
+ throw new IdentityResolverException(String.format(
+ "Identity resolver '%s' failed, response did not include 'sub'",
+ _userInfoEndpoint));
+ }
+ return new UsernamePrincipal(googleId);
+ }
+ }
+}
Added: qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/oauth2/google/GoogleOAuth2IdentityResolverServiceFactory.java
URL: http://svn.apache.org/viewvc/qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/oauth2/google/GoogleOAuth2IdentityResolverServiceFactory.java?rev=1729215&view=auto
==============================================================================
--- qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/oauth2/google/GoogleOAuth2IdentityResolverServiceFactory.java (added)
+++ qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/oauth2/google/GoogleOAuth2IdentityResolverServiceFactory.java Mon Feb 8 17:29:44 2016
@@ -0,0 +1,45 @@
+/*
+ *
+ * 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.qpid.server.security.auth.manager.oauth2.google;
+
+import org.apache.qpid.server.plugin.PluggableService;
+import org.apache.qpid.server.security.auth.manager.oauth2.OAuth2AuthenticationProvider;
+import org.apache.qpid.server.security.auth.manager.oauth2.OAuth2IdentityResolverService;
+import org.apache.qpid.server.security.auth.manager.oauth2.OAuth2IdentityResolverServiceFactory;
+
+@PluggableService
+public class GoogleOAuth2IdentityResolverServiceFactory implements OAuth2IdentityResolverServiceFactory
+{
+ public static final String TYPE = "GoogleUserInfo";
+
+ @Override
+ public OAuth2IdentityResolverService createIdentityResolverService(final OAuth2AuthenticationProvider authenticationProvider)
+ {
+ return new GoogleOAuth2IdentityResolverService(authenticationProvider);
+ }
+
+ @Override
+ public String getType()
+ {
+ return TYPE;
+ }
+}
Added: qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/oauth2/google/package-info.java
URL: http://svn.apache.org/viewvc/qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/oauth2/google/package-info.java?rev=1729215&view=auto
==============================================================================
--- qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/oauth2/google/package-info.java (added)
+++ qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/oauth2/google/package-info.java Mon Feb 8 17:29:44 2016
@@ -0,0 +1,44 @@
+/*
+ *
+ * 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.
+ *
+ */
+
+/**
+ * Identity resolver utilising Google's OAuth 2.0 userinfo endpoint
+ * <p>
+ * To use Google as an authentication provider, the OAuth2Authentication
+ * needs to be configured to co-operate with the identity resolver like so:
+ *
+ * <pre>
+ * "type" : "OAuth2",
+ * "authorizationEndpointURI" : "https://accounts.google.com/o/oauth2/v2/auth",
+ * "tokenEndpointURI" : "https://www.googleapis.com/oauth2/v4/token",
+ * "tokenEndpointNeedsAuth" : false,
+ * "identityResolverFactoryType" : "GoogleUserInfo",
+ * "identityResolverEndpointURI" : "https://www.googleapis.com/oauth2/v3/userinfo",
+ * "clientId" : "......",
+ * "clientSecret" : "....",
+ * "scope" : "profile"
+ * </pre>
+ *
+ * Note that when configuring the Authorized redirect URIs in the Google Developer Console
+ * include the trailing slash e.g. https://localhost:8080/.
+ * </p>
+ */
+package org.apache.qpid.server.security.auth.manager.oauth2.google;
Added: qpid/java/trunk/broker-core/src/test/java/org/apache/qpid/server/security/auth/manager/oauth2/OAuth2SaslServerTest.java
URL: http://svn.apache.org/viewvc/qpid/java/trunk/broker-core/src/test/java/org/apache/qpid/server/security/auth/manager/oauth2/OAuth2SaslServerTest.java?rev=1729215&view=auto
==============================================================================
--- qpid/java/trunk/broker-core/src/test/java/org/apache/qpid/server/security/auth/manager/oauth2/OAuth2SaslServerTest.java (added)
+++ qpid/java/trunk/broker-core/src/test/java/org/apache/qpid/server/security/auth/manager/oauth2/OAuth2SaslServerTest.java Mon Feb 8 17:29:44 2016
@@ -0,0 +1,90 @@
+/*
+ *
+ * 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.qpid.server.security.auth.manager.oauth2;
+
+import javax.security.sasl.SaslException;
+
+import org.apache.qpid.test.utils.QpidTestCase;
+
+public class OAuth2SaslServerTest extends QpidTestCase
+{
+
+ private OAuth2SaslServer _server = new OAuth2SaslServer();
+
+ public void testEvaluateResponse_ResponseHasAuthOnly() throws Exception
+ {
+ assertFalse(_server.isComplete());
+ _server.evaluateResponse("auth=Bearer token\1\1".getBytes());
+ assertTrue(_server.isComplete());
+ assertEquals("token", _server.getNegotiatedProperty(OAuth2SaslServer.ACCESS_TOKEN_PROPERTY));
+ }
+
+ public void testEvaluateResponse_ResponseAuthAndOthers() throws Exception
+ {
+ _server.evaluateResponse("user=xxx\1auth=Bearer token\1host=localhost\1\1".getBytes());
+ assertEquals("token", _server.getNegotiatedProperty(OAuth2SaslServer.ACCESS_TOKEN_PROPERTY));
+ }
+
+ public void testEvaluateResponse_ResponseAuthAbsent() throws Exception
+ {
+ try
+ {
+ _server.evaluateResponse("host=localhost\1\1".getBytes());
+ fail("Exception not thrown");
+ }
+ catch (SaslException se)
+ {
+ // PASS
+ }
+ assertFalse(_server.isComplete());
+ }
+
+ public void testEvaluateResponse_ResponseAuthMalformed() throws Exception
+ {
+ try
+ {
+ _server.evaluateResponse("auth=wibble\1\1".getBytes());
+ fail("Exception not thrown");
+ }
+ catch (SaslException se)
+ {
+ // PASS
+ }
+ assertFalse(_server.isComplete());
+ }
+
+ public void testEvaluateResponse_PrematureGetNegotiatedProperty() throws Exception
+ {
+ try
+ {
+ _server.getNegotiatedProperty(OAuth2SaslServer.ACCESS_TOKEN_PROPERTY);
+ }
+ catch (IllegalStateException ise)
+ {
+ // PASS
+ }
+
+ _server.evaluateResponse("auth=Bearer token\1\1".getBytes());
+ assertEquals("token", _server.getNegotiatedProperty(OAuth2SaslServer.ACCESS_TOKEN_PROPERTY));
+ }
+
+}
Added: qpid/java/trunk/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/auth/OAuth2InteractiveAuthenticator.java
URL: http://svn.apache.org/viewvc/qpid/java/trunk/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/auth/OAuth2InteractiveAuthenticator.java?rev=1729215&view=auto
==============================================================================
--- qpid/java/trunk/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/auth/OAuth2InteractiveAuthenticator.java (added)
+++ qpid/java/trunk/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/auth/OAuth2InteractiveAuthenticator.java Mon Feb 8 17:29:44 2016
@@ -0,0 +1,271 @@
+/*
+ * 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.qpid.server.management.plugin.auth;
+
+import java.io.IOException;
+import java.security.SecureRandom;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.security.auth.Subject;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+import javax.xml.bind.DatatypeConverter;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.apache.qpid.server.management.plugin.HttpManagementConfiguration;
+import org.apache.qpid.server.management.plugin.HttpManagementUtil;
+import org.apache.qpid.server.management.plugin.HttpRequestInteractiveAuthenticator;
+import org.apache.qpid.server.management.plugin.servlet.ServletConnectionPrincipal;
+import org.apache.qpid.server.model.Broker;
+import org.apache.qpid.server.plugin.PluggableService;
+import org.apache.qpid.server.security.SubjectCreator;
+import org.apache.qpid.server.security.auth.AuthenticationResult;
+import org.apache.qpid.server.security.auth.SubjectAuthenticationResult;
+import org.apache.qpid.server.security.auth.manager.oauth2.OAuth2AuthenticationProvider;
+import org.apache.qpid.server.security.auth.manager.oauth2.OAuth2Utils;
+
+@PluggableService
+public class OAuth2InteractiveAuthenticator implements HttpRequestInteractiveAuthenticator
+{
+ private static final Logger LOGGER = LoggerFactory.getLogger(OAuth2InteractiveAuthenticator.class);
+ private static final int STATE_NONCE_BIT_SIZE = 256;
+ private static final String STATE_NAME = "stateNonce";
+ private static final String TYPE = "OAuth2";
+
+ private SecureRandom _random = new SecureRandom();
+
+ @Override
+ public String getType()
+ {
+ return TYPE;
+ }
+
+ @Override
+ public AuthenticationHandler getAuthenticationHandler(final HttpServletRequest request,
+ final HttpManagementConfiguration configuration)
+ {
+ if (configuration.getAuthenticationProvider(request) instanceof OAuth2AuthenticationProvider)
+ {
+ final OAuth2AuthenticationProvider oauth2Provider =
+ (OAuth2AuthenticationProvider) configuration.getAuthenticationProvider(request);
+ final Map<String, String> requestParameters;
+ try
+ {
+ requestParameters = getRequestParameters(request);
+ }
+ catch (IllegalArgumentException e)
+ {
+ return new FailedAuthenticationHandler(400, "Some request parameters are included more than once " + request, e);
+ }
+
+ final String authorizationCode = requestParameters.get("code");
+ if (authorizationCode == null)
+ {
+ final String authorizationRedirectURL = buildAuthorizationRedirectURL(request, oauth2Provider);
+ return new AuthenticationHandler()
+ {
+ @Override
+ public void handleAuthentication(final HttpServletResponse response) throws IOException
+ {
+ LOGGER.debug("Sending redirect to authorization endpoint {}", oauth2Provider.getAuthorizationEndpointURI());
+ response.sendRedirect(authorizationRedirectURL);
+ }
+ };
+ }
+ else
+ {
+ final HttpSession httpSession = request.getSession();
+ String state = requestParameters.get("state");
+
+ if (state == null)
+ {
+ LOGGER.warn("Deny login attempt with wrong state: {}", state);
+ return new FailedAuthenticationHandler(400, "no state set on request with authorization code grant: "
+ + request);
+ }
+ if (!checkState(httpSession, state))
+ {
+ LOGGER.warn("Deny login attempt with wrong state: {}", state);
+ return new FailedAuthenticationHandler(401, "Received request with wrong state: " + state);
+ }
+ final String redirectUri = (String) httpSession.getAttribute("redirectUri");
+ return new AuthenticationHandler()
+ {
+ @Override
+ public void handleAuthentication(final HttpServletResponse response) throws IOException
+ {
+ AuthenticationResult authenticationResult = oauth2Provider.authenticateViaAuthorizationCode(authorizationCode, redirectUri);
+ createSubject(authenticationResult);
+
+ LOGGER.debug("Successful login. Redirect to original resource {}", redirectUri);
+ response.sendRedirect(redirectUri);
+ }
+
+ private void createSubject(final AuthenticationResult authenticationResult)
+ {
+ String username = authenticationResult.getMainPrincipal().getName();
+
+ SubjectCreator subjectCreator = oauth2Provider.getSubjectCreator(request.isSecure());
+ SubjectAuthenticationResult
+ result = subjectCreator.createResultWithGroups(username, authenticationResult);
+
+ Subject subject = result.getSubject();
+
+ if (subject == null)
+ {
+ throw new SecurityException("Only authenticated users can access the management interface");
+ }
+
+ Subject original = subject;
+ subject = new Subject(false,
+ original.getPrincipals(),
+ original.getPublicCredentials(),
+ original.getPrivateCredentials());
+ subject.getPrincipals().add(new ServletConnectionPrincipal(request));
+ subject.setReadOnly();
+
+ Broker broker = (Broker) oauth2Provider.getParent(Broker.class);
+ HttpManagementUtil.assertManagementAccess(broker.getSecurityManager(), subject);
+
+ HttpManagementUtil.saveAuthorisedSubject(httpSession, subject);
+ }
+ };
+ }
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ private String buildAuthorizationRedirectURL(final HttpServletRequest request,
+ final OAuth2AuthenticationProvider oauth2Provider)
+ {
+ final String redirectUri = getRedirectUri(request);
+ final String authorizationEndpoint = oauth2Provider.getAuthorizationEndpointURI().toString();
+ final HttpSession httpSession = request.getSession();
+ httpSession.setAttribute("redirectUri", redirectUri);
+
+ Map<String, String> queryArgs = new HashMap<>();
+ queryArgs.put("client_id", oauth2Provider.getClientId());
+ queryArgs.put("redirect_uri", redirectUri);
+ queryArgs.put("response_type", "code");
+ queryArgs.put("state", createState(httpSession));
+ if (oauth2Provider.getScope() != null)
+ {
+ queryArgs.put("scope", oauth2Provider.getScope());
+ }
+
+ // TODO: currently we assume, but don't check, that the authorizationEndpointURI does not contain a query string
+ StringBuilder urlBuilder = new StringBuilder(authorizationEndpoint);
+ urlBuilder.append("?");
+ urlBuilder.append(OAuth2Utils.buildRequestQuery(queryArgs));
+
+ return urlBuilder.toString();
+ }
+
+ private Map<String, String> getRequestParameters(final HttpServletRequest request)
+ {
+ Map<String, String> requestParameters = new HashMap<>();
+ Enumeration<String> parameterNames = request.getParameterNames();
+ while (parameterNames.hasMoreElements())
+ {
+ String parameterName = parameterNames.nextElement();
+ String[] parameters = request.getParameterValues(parameterName);
+ if (parameters == null)
+ {
+ throw new IllegalArgumentException(String.format("Request parameter '%s' is null", parameterName));
+ }
+ if (parameters.length != 1)
+ {
+ // having a parameter more than once violates the OAuth2 spec: http://tools.ietf.org/html/rfc6749#section-3.1
+ throw new IllegalArgumentException(String.format("Request parameter '%s' MUST NOT occur more than once",
+ parameterName));
+ }
+ requestParameters.put(parameterName, parameters[0]);
+ }
+ return requestParameters;
+ }
+
+ private String getRedirectUri(final HttpServletRequest request)
+ {
+ StringBuffer redirectUri = request.getRequestURL();
+ final String queryString = request.getQueryString();
+ if (queryString != null)
+ {
+ redirectUri.append(queryString);
+ }
+ return redirectUri.toString();
+ }
+
+ private String createState(HttpSession session)
+ {
+ byte[] nonceBytes = new byte[STATE_NONCE_BIT_SIZE / 8];
+ _random.nextBytes(nonceBytes);
+
+ String nonce = DatatypeConverter.printBase64Binary(nonceBytes);
+ session.setAttribute(STATE_NAME, nonce);
+ return nonce;
+ }
+
+ private boolean checkState(HttpSession session, String state)
+ {
+ String nonce = (String) session.getAttribute(STATE_NAME);
+ session.removeAttribute(STATE_NAME);
+ return state != null && state.equals(nonce);
+ }
+
+ class FailedAuthenticationHandler implements AuthenticationHandler
+ {
+ private final int _errorCode;
+ private final Throwable _throwable;
+ private final String _message;
+
+ FailedAuthenticationHandler(int errorCode, String message)
+ {
+ this(errorCode, message, null);
+ }
+
+ FailedAuthenticationHandler(int errorCode, String message, Throwable t)
+ {
+ _errorCode = errorCode;
+ _message = message;
+ _throwable = t;
+ }
+
+ @Override
+ public void handleAuthentication(final HttpServletResponse response) throws IOException
+ {
+ if (_throwable != null)
+ {
+ response.sendError(_errorCode, _message + ": " + _throwable);
+ }
+ else
+ {
+ response.sendError(_errorCode, _message);
+ }
+ }
+ }
+}
Added: qpid/java/trunk/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/auth/OAuth2PreemptiveAuthenticator.java
URL: http://svn.apache.org/viewvc/qpid/java/trunk/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/auth/OAuth2PreemptiveAuthenticator.java?rev=1729215&view=auto
==============================================================================
--- qpid/java/trunk/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/auth/OAuth2PreemptiveAuthenticator.java (added)
+++ qpid/java/trunk/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/auth/OAuth2PreemptiveAuthenticator.java Mon Feb 8 17:29:44 2016
@@ -0,0 +1,84 @@
+/*
+ *
+ * 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.qpid.server.management.plugin.auth;
+
+import java.security.Principal;
+
+import javax.security.auth.Subject;
+import javax.servlet.http.HttpServletRequest;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.apache.qpid.server.management.plugin.HttpManagementConfiguration;
+import org.apache.qpid.server.management.plugin.HttpRequestPreemptiveAuthenticator;
+import org.apache.qpid.server.model.AuthenticationProvider;
+import org.apache.qpid.server.plugin.PluggableService;
+import org.apache.qpid.server.security.SubjectCreator;
+import org.apache.qpid.server.security.auth.AuthenticationResult;
+import org.apache.qpid.server.security.auth.SubjectAuthenticationResult;
+import org.apache.qpid.server.security.auth.manager.oauth2.OAuth2AuthenticationProvider;
+
+@PluggableService
+public class OAuth2PreemptiveAuthenticator implements HttpRequestPreemptiveAuthenticator
+{
+ private static final Logger LOGGER = LoggerFactory.getLogger(OAuth2PreemptiveAuthenticator.class);
+ private static final String TYPE = "OAuth2";
+ private static final String BEARER_PREFIX = "Bearer ";
+
+ @Override
+ public Subject attemptAuthentication(final HttpServletRequest request,
+ final HttpManagementConfiguration configuration)
+ {
+ final AuthenticationProvider<?> authenticationProvider = configuration.getAuthenticationProvider(request);
+ String authorizationHeader = request.getHeader("Authorization");
+ String accessToken = null;
+
+ if (authorizationHeader != null && authorizationHeader.startsWith(BEARER_PREFIX))
+ {
+ accessToken = authorizationHeader.substring(BEARER_PREFIX.length());
+ }
+
+ if (accessToken != null && authenticationProvider instanceof OAuth2AuthenticationProvider)
+ {
+ OAuth2AuthenticationProvider<?> oAuth2AuthProvider = (OAuth2AuthenticationProvider<?>) authenticationProvider;
+ AuthenticationResult authenticationResult = oAuth2AuthProvider.authenticateViaAccessToken(accessToken);
+ Principal mainPrincipal = authenticationResult.getMainPrincipal();
+ if (mainPrincipal == null)
+ {
+ LOGGER.debug("Preemptive OAuth2 authentication failed", authenticationResult.getCause());
+ return null;
+ }
+
+ SubjectCreator subjectCreator = authenticationProvider.getSubjectCreator(request.isSecure());
+ SubjectAuthenticationResult result = subjectCreator.createResultWithGroups(mainPrincipal.getName(), authenticationResult);
+
+ return result.getSubject();
+ }
+ return null;
+ }
+
+ @Override
+ public String getType()
+ {
+ return TYPE;
+ }
+}
Modified: qpid/java/trunk/client/src/main/java/org/apache/qpid/client/security/CallbackHandlerRegistry.properties
URL: http://svn.apache.org/viewvc/qpid/java/trunk/client/src/main/java/org/apache/qpid/client/security/CallbackHandlerRegistry.properties?rev=1729215&r1=1729214&r2=1729215&view=diff
==============================================================================
--- qpid/java/trunk/client/src/main/java/org/apache/qpid/client/security/CallbackHandlerRegistry.properties (original)
+++ qpid/java/trunk/client/src/main/java/org/apache/qpid/client/security/CallbackHandlerRegistry.properties Mon Feb 8 17:29:44 2016
@@ -33,4 +33,5 @@ CRAM-MD5.6=org.apache.qpid.client.securi
PLAIN.7=org.apache.qpid.client.security.UsernamePasswordCallbackHandler
AMQPLAIN.8=org.apache.qpid.client.security.UsernamePasswordCallbackHandler
ANONYMOUS.9=org.apache.qpid.client.security.UsernamePasswordCallbackHandler
+XOAUTH2.10=org.apache.qpid.client.security.oauth2.OAuth2AccessTokenCallbackHandler
Modified: qpid/java/trunk/client/src/main/java/org/apache/qpid/client/security/DynamicSaslRegistrar.properties
URL: http://svn.apache.org/viewvc/qpid/java/trunk/client/src/main/java/org/apache/qpid/client/security/DynamicSaslRegistrar.properties?rev=1729215&r1=1729214&r2=1729215&view=diff
==============================================================================
--- qpid/java/trunk/client/src/main/java/org/apache/qpid/client/security/DynamicSaslRegistrar.properties (original)
+++ qpid/java/trunk/client/src/main/java/org/apache/qpid/client/security/DynamicSaslRegistrar.properties Mon Feb 8 17:29:44 2016
@@ -21,3 +21,4 @@ CRAM-MD5-HASHED=org.apache.qpid.client.s
ANONYMOUS=org.apache.qpid.client.security.anonymous.AnonymousSaslClientFactory
SCRAM-SHA-1=org.apache.qpid.client.security.scram.ScramSHA1SaslClientFactory
SCRAM-SHA-256=org.apache.qpid.client.security.scram.ScramSHA256SaslClientFactory
+XOAUTH2=org.apache.qpid.client.security.oauth2.OAuth2SaslClientFactory
Added: qpid/java/trunk/client/src/main/java/org/apache/qpid/client/security/oauth2/OAuth2AccessTokenCallbackHandler.java
URL: http://svn.apache.org/viewvc/qpid/java/trunk/client/src/main/java/org/apache/qpid/client/security/oauth2/OAuth2AccessTokenCallbackHandler.java?rev=1729215&view=auto
==============================================================================
--- qpid/java/trunk/client/src/main/java/org/apache/qpid/client/security/oauth2/OAuth2AccessTokenCallbackHandler.java (added)
+++ qpid/java/trunk/client/src/main/java/org/apache/qpid/client/security/oauth2/OAuth2AccessTokenCallbackHandler.java Mon Feb 8 17:29:44 2016
@@ -0,0 +1,56 @@
+/*
+ *
+ * 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.qpid.client.security.oauth2;
+
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.PasswordCallback;
+import javax.security.auth.callback.UnsupportedCallbackException;
+
+import org.apache.qpid.client.security.AMQCallbackHandler;
+import org.apache.qpid.jms.ConnectionURL;
+
+public class OAuth2AccessTokenCallbackHandler implements AMQCallbackHandler
+{
+ private ConnectionURL _connectionURL;
+
+ @Override
+ public void initialise(ConnectionURL connectionURL)
+ {
+ _connectionURL = connectionURL;
+ }
+
+ @Override
+ public void handle(Callback[] callbacks) throws UnsupportedCallbackException
+ {
+ for(Callback cb : callbacks)
+ {
+ if (cb instanceof PasswordCallback)
+ {
+ ((PasswordCallback) cb).setPassword(_connectionURL.getPassword().toCharArray());
+ }
+ else
+ {
+ throw new UnsupportedCallbackException(cb);
+ }
+ }
+ }
+}
Added: qpid/java/trunk/client/src/main/java/org/apache/qpid/client/security/oauth2/OAuth2SaslClient.java
URL: http://svn.apache.org/viewvc/qpid/java/trunk/client/src/main/java/org/apache/qpid/client/security/oauth2/OAuth2SaslClient.java?rev=1729215&view=auto
==============================================================================
--- qpid/java/trunk/client/src/main/java/org/apache/qpid/client/security/oauth2/OAuth2SaslClient.java (added)
+++ qpid/java/trunk/client/src/main/java/org/apache/qpid/client/security/oauth2/OAuth2SaslClient.java Mon Feb 8 17:29:44 2016
@@ -0,0 +1,118 @@
+/*
+ *
+ * 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.qpid.client.security.oauth2;
+
+import java.io.IOException;
+
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.PasswordCallback;
+import javax.security.auth.callback.UnsupportedCallbackException;
+import javax.security.sasl.SaslClient;
+import javax.security.sasl.SaslException;
+
+public class OAuth2SaslClient implements SaslClient
+{
+ public static final String MECHANISM = "XOAUTH2";
+ private final CallbackHandler _callbackHandler;
+ private boolean _isComplete = false;
+
+ public OAuth2SaslClient(CallbackHandler callbackHandler)
+ {
+ _callbackHandler = callbackHandler;
+ }
+
+ @Override
+ public String getMechanismName()
+ {
+ return MECHANISM;
+ }
+
+ @Override
+ public boolean hasInitialResponse()
+ {
+ return true;
+ }
+
+ @Override
+ public byte[] evaluateChallenge(byte[] challenge) throws SaslException
+ {
+ if (_isComplete)
+ {
+ return new byte[0];
+ }
+
+ PasswordCallback callback = new PasswordCallback("promptunused", false);
+ Callback[] callbacks = new Callback[] {callback};
+ try
+ {
+ _callbackHandler.handle(callbacks);
+ }
+ catch (UnsupportedCallbackException e)
+ {
+ throw new SaslException("Unsupported callback", e);
+ }
+ catch (IOException e)
+ {
+ throw new SaslException("Failed to execute callback", e);
+ }
+
+ String accessToken = new String(callback.getPassword());
+ if (accessToken == null || accessToken.length() == 0)
+ {
+ throw new SaslException("OAuth2SaslClient requires that the OAuth2 access token is"
+ + " supplied via the connection's password");
+ }
+ byte[] response = String.format("auth=Bearer %s\1\1", accessToken).getBytes();
+ _isComplete = true;
+ return response;
+ }
+
+ @Override
+ public boolean isComplete()
+ {
+ return _isComplete;
+ }
+
+ @Override
+ public Object getNegotiatedProperty(String propName)
+ {
+ return null;
+ }
+
+ @Override
+ public byte[] unwrap(byte[] incoming, int offset, int len) throws SaslException
+ {
+ throw new SaslException();
+ }
+
+ @Override
+ public byte[] wrap(byte[] outgoing, int offset, int len) throws SaslException
+ {
+ throw new SaslException();
+ }
+
+ @Override
+ public void dispose() throws SaslException
+ {
+ }
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@qpid.apache.org
For additional commands, e-mail: commits-help@qpid.apache.org