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: [email protected]
For additional commands, e-mail: [email protected]