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