From 4ea62a251fdd6a641a86ef5629446b43941db452 Mon Sep 17 00:00:00 2001 From: Albert Cheng Date: Mon, 13 Aug 2012 23:10:03 -0700 Subject: [PATCH 1/2] Implementation of Authentication for Media Service. --- .../services/media/ActiveToken.java | 67 +++++++ .../windowsazure/services/media/Exports.java | 31 ++++ .../services/media/MediaConfiguration.java | 155 ++++++++++++++++ .../services/media/OAuthContract.java | 44 +++++ .../services/media/OAuthFilter.java | 63 +++++++ .../services/media/OAuthRestProxy.java | 116 ++++++++++++ .../services/media/OAuthTokenManager.java | 110 +++++++++++ .../services/media/OAuthTokenResponse.java | 71 ++++++++ .../media/OAuthRestProxyIntegrationTest.java | 55 ++++++ .../services/media/OAuthTokenManagerTest.java | 171 ++++++++++++++++++ 10 files changed, 883 insertions(+) create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/media/ActiveToken.java create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/media/Exports.java create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/media/MediaConfiguration.java create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/media/OAuthContract.java create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/media/OAuthFilter.java create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/media/OAuthRestProxy.java create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/media/OAuthTokenManager.java create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/media/OAuthTokenResponse.java create mode 100644 microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/media/OAuthRestProxyIntegrationTest.java create mode 100644 microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/media/OAuthTokenManagerTest.java diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/media/ActiveToken.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/media/ActiveToken.java new file mode 100644 index 0000000000000..73e1564be1f27 --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/media/ActiveToken.java @@ -0,0 +1,67 @@ +/** + * Copyright 2011 Microsoft Corporation + * + * Licensed 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 com.microsoft.windowsazure.services.media; + +import java.util.Date; + +/** + * A class representing active token for OAuthTokenResponse. + * + * @author azurejava@microsoft.com + * + */ +public class ActiveToken { + + private Date expiresUtc; + private OAuthTokenResponse oAuthTokenResponse; + + /** + * Gets the expiration time in UTC. + * + * @return The token expiration time in UTC. + */ + public Date getExpiresUtc() { + return expiresUtc; + } + + /** + * Sets the token expiration time in UTC. + * + * @param expiresUtc + */ + public void setExpiresUtc(Date expiresUtc) { + this.expiresUtc = expiresUtc; + } + + /** + * Gets the OAuth token response. + * + * @return The OAuth token response. + */ + public OAuthTokenResponse getOAuthTokenResponse() { + return oAuthTokenResponse; + } + + /** + * Sets the OAuth token response. + * + * @param oAuth2TokenResponse + */ + public void setOAuth2TokenResponse(OAuthTokenResponse oAuth2TokenResponse) { + this.oAuthTokenResponse = oAuth2TokenResponse; + } + +} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/media/Exports.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/media/Exports.java new file mode 100644 index 0000000000000..2a9cd99c4b38c --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/media/Exports.java @@ -0,0 +1,31 @@ +/** + * Copyright 2011 Microsoft Corporation + * + * Licensed 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 com.microsoft.windowsazure.services.media; + +import com.microsoft.windowsazure.services.core.Builder; + +public class Exports implements Builder.Exports { + + /** + * register the OAUTH service. + */ + @Override + public void register(Builder.Registry registry) { + registry.add(OAuthContract.class, OAuthRestProxy.class); + registry.add(OAuthTokenManager.class); + registry.add(OAuthFilter.class); + } + +} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/media/MediaConfiguration.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/media/MediaConfiguration.java new file mode 100644 index 0000000000000..0c4c703fb1dd7 --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/media/MediaConfiguration.java @@ -0,0 +1,155 @@ +/** + * Copyright 2011 Microsoft Corporation + * + * Licensed 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 com.microsoft.windowsazure.services.media; + +import com.microsoft.windowsazure.services.core.Configuration; + +/** + * Provides functionality to create a service bus configuration. + * + */ +public class MediaConfiguration { + + /** + * Defines the media service configuration URI constant. + * + */ + public static final String URI = "media.uri"; + + /** + * Defines the OAUTH configuration URI constant. + * + */ + public static final String OAUTH_URI = "oauth.uri"; + + /** + * Defines the OAUTH configuration client ID constant. + * + */ + public static final String OAUTH_CLIENT_ID = "oauth.client.id"; + + /** + * Defines the OAUTH configuration client secret constant. + * + */ + public static final String OAUTH_CLIENT_SECRET = "oauth.client.secret"; + + /** + * Defines the SCOPE of the media service sent to OAUTH. + */ + public static final String OAUTH_SCOPE = "oauth.scope"; + + /** + * Creates a media service configuration using the specified media service base URI, OAUTH URI, + * client ID, and client secret. + * + * @param mediaServiceBaseUri + * A String object that represents the media service base URI. + * + * @param oAuthUri + * A String object that represents the OAUTH URI. + * + * @param clientId + * A String object that represents the client ID. + * + * @param clientSecret + * A String object that represents the client secret. + * + * @return + * A Configuration object that can be used when creating an instance of the + * MediaService class. + * + */ + public static Configuration configureWithOAuthAuthentication(String mediaServiceBaseUri, String oAuthUri, + String clientId, String clientSecret) { + return configureWithOAuthAuthentication(null, Configuration.getInstance(), mediaServiceBaseUri, oAuthUri, + clientId, clientSecret); + } + + /** + * Creates a media service configuration using the specified configuration, media service base URI, OAuth URI, + * client ID, and client secret. + * + * @param configuration + * A previously instantiated Configuration object. + * + * @param mediaServiceBaseUri + * A String object that represents the base URI of Media service. + * + * @param oAuthUri + * A String object that represents the URI of OAuth service. + * + * @param clientId + * A String object that represents the client ID. + * + * @param clientSecret + * A String object that represents the client secret. + * + * @return + * A Configuration object that can be used when creating an instance of the + * MediaService class. + * + */ + public static Configuration configureWithOAuthAuthentication(Configuration configuration, + String mediaServiceBaseUri, String oAuthUri, String clientId, String clientSecret) { + return configureWithOAuthAuthentication(null, configuration, mediaServiceBaseUri, oAuthUri, clientId, + clientSecret); + } + + /** + * Creates a media service configuration using the specified profile, configuration, media service base URI, + * OAuth URI, client ID, and client secret. + * + * @param profile + * A String object that represents the profile. + * + * @param configuration + * A previously instantiated Configuration object. + * + * @param mediaServiceBaseUri + * A String object that represents the base URI of media service. + * + * @param oAuthUri + * A String object that represents the URI of OAUTH service. + * + * @param clientId + * A String object that represents the client ID. + * + * @param clientSecret + * A String object that represents the client secret. + * + * @return + * A Configuration object that can be used when creating an instance of the + * MediaService class. + * + */ + public static Configuration configureWithOAuthAuthentication(String profile, Configuration configuration, + String mediaServiceBaseUri, String oAuthUri, String clientId, String clientSecret) { + + if (profile == null) { + profile = ""; + } + else if (profile.length() != 0 && !profile.endsWith(".")) { + profile = profile + "."; + } + + configuration.setProperty(profile + URI, "https://" + mediaServiceBaseUri); + configuration.setProperty(profile + OAUTH_URI, oAuthUri); + configuration.setProperty(profile + OAUTH_CLIENT_ID, clientId); + configuration.setProperty(profile + OAUTH_CLIENT_SECRET, clientSecret); + + return configuration; + } +} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/media/OAuthContract.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/media/OAuthContract.java new file mode 100644 index 0000000000000..b74216823c308 --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/media/OAuthContract.java @@ -0,0 +1,44 @@ +/** + * Copyright 2011 Microsoft Corporation + * + * Licensed 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 com.microsoft.windowsazure.services.media; + +import java.net.URI; + +import com.microsoft.windowsazure.services.core.ServiceException; + +public interface OAuthContract { + /** + * Gets an OAuth access token with specified OAUTH URI, client ID, client secret, and scope. + * + * @param oAuthUri + * A URI object which represents an OAUTH URI. + * + * @param clientId + * A String object which represents a client ID. + * + * @param clientSecret + * A String object which represents a client secret. + * + * @param scope + * A String object which represents the scope. + * + * @return OAuthTokenResponse + * @throws ServiceException + */ + public OAuthTokenResponse getAccessToken(URI oAuthUri, String clientId, String clientSecret, String scope) + throws ServiceException; + +} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/media/OAuthFilter.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/media/OAuthFilter.java new file mode 100644 index 0000000000000..c38d53dc166c7 --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/media/OAuthFilter.java @@ -0,0 +1,63 @@ +/* + * Copyright 2011 Microsoft Corporation + * + * Licensed 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 com.microsoft.windowsazure.services.media; + +import java.net.URISyntaxException; + +import com.microsoft.windowsazure.services.core.ServiceException; +import com.sun.jersey.api.client.ClientHandlerException; +import com.sun.jersey.api.client.ClientRequest; +import com.sun.jersey.api.client.ClientResponse; +import com.sun.jersey.api.client.filter.ClientFilter; + +public class OAuthFilter extends ClientFilter { + private final OAuthTokenManager oAuthTokenManager; + + /** + * Creates an OAuthFilter object with specfied OAuthTokenManager instance. + * + * @param oAuthTokenManager + */ + public OAuthFilter(OAuthTokenManager oAuthTokenManager) { + this.oAuthTokenManager = oAuthTokenManager; + } + + /** + * Handles response with a specified client request. + * + * @param clientRequest + * A ClientRequest object representing a client request. + */ + @Override + public ClientResponse handle(ClientRequest clientRequest) throws ClientHandlerException { + + String accessToken; + try { + accessToken = oAuthTokenManager.getAccessToken(clientRequest.getURI().toString()); + } + catch (ServiceException e) { + // must wrap exception because of base class signature + throw new ClientHandlerException(e); + } + catch (URISyntaxException e) { + // must wrap exception because of base class signature + throw new ClientHandlerException(e); + } + + clientRequest.getHeaders().add("Authorization", "Bearer " + accessToken); + + return this.getNext().handle(clientRequest); + } +} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/media/OAuthRestProxy.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/media/OAuthRestProxy.java new file mode 100644 index 0000000000000..cedae2640ffb7 --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/media/OAuthRestProxy.java @@ -0,0 +1,116 @@ +/** + * Copyright 2011 Microsoft Corporation + * + * Licensed 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 com.microsoft.windowsazure.services.media; + +import java.io.IOException; +import java.net.URI; + +import javax.inject.Inject; +import javax.ws.rs.core.MediaType; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.codehaus.jackson.JsonParseException; +import org.codehaus.jackson.map.JsonMappingException; +import org.codehaus.jackson.map.ObjectMapper; +import org.codehaus.jackson.type.TypeReference; + +import com.microsoft.windowsazure.services.core.ServiceException; +import com.microsoft.windowsazure.services.core.utils.ServiceExceptionFactory; +import com.sun.jersey.api.client.Client; +import com.sun.jersey.api.client.ClientResponse; +import com.sun.jersey.api.client.UniformInterfaceException; +import com.sun.jersey.api.representation.Form; + +public class OAuthRestProxy implements OAuthContract { + Client channel; + + private final String _grantType = "client_credentials"; + + static Log log = LogFactory.getLog(OAuthContract.class); + + @Inject + public OAuthRestProxy(Client channel) { + this.channel = channel; + } + + /** + * Gets an OAuth access token with specified OAUTH URI, client ID, client secret, and scope. + * + * @param oAuthUri + * A URI object which represents an OAUTH URI. + * + * @param clientId + * A String object which represents a client ID. + * + * @param clientSecret + * A String object which represents a client secret. + * + * @param scope + * A String object which represents the scope. + * + * @return OAuthTokenResponse + * @throws ServiceException + */ + @Override + public OAuthTokenResponse getAccessToken(URI oAuthUri, String clientId, String clientSecret, String scope) + throws ServiceException { + OAuthTokenResponse response = null; + Form requestForm = new Form(); + ClientResponse clientResponse; + String responseJson; + + requestForm.add("grant_type", _grantType); + requestForm.add("client_id", clientId); + requestForm.add("client_secret", clientSecret); + requestForm.add("scope", scope); + + try { + clientResponse = channel.resource(oAuthUri).accept(MediaType.APPLICATION_FORM_URLENCODED) + .type(MediaType.APPLICATION_FORM_URLENCODED).post(ClientResponse.class, requestForm); + } + catch (UniformInterfaceException e) { + log.warn("OAuth server returned error acquiring access_token", e); + throw ServiceExceptionFactory.process("OAuth", new ServiceException( + "OAuth server returned error acquiring access_token", e)); + } + + responseJson = clientResponse.getEntity(String.class); + + try { + ObjectMapper mapper = new ObjectMapper(); + TypeReference typeReference = new TypeReference() { + }; + response = mapper.readValue(responseJson, typeReference); + } + catch (JsonParseException e) { + log.warn("The response from OAuth server cannot be parsed correctly", e); + throw ServiceExceptionFactory.process("OAuth", new ServiceException( + "The response from OAuth server cannot be parsed correctly", e)); + } + catch (JsonMappingException e) { + log.warn("The response from OAuth server cannot be mapped to OAuthResponse object", e); + throw ServiceExceptionFactory.process("OAuth", new ServiceException( + "The response from OAuth server cannot be mapped to OAuthResponse object", e)); + } + catch (IOException e) { + log.warn("Cannot map the response from OAuth server correctly.", e); + throw ServiceExceptionFactory.process("OAuth", new ServiceException( + "Cannot map the response from OAuth server correctly.", e)); + } + + return response; + } +} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/media/OAuthTokenManager.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/media/OAuthTokenManager.java new file mode 100644 index 0000000000000..111aba7e33d20 --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/media/OAuthTokenManager.java @@ -0,0 +1,110 @@ +/** + * Copyright 2011 Microsoft Corporation + * + * Licensed 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 com.microsoft.windowsazure.services.media; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Date; +import java.util.Iterator; +import java.util.Map.Entry; +import java.util.concurrent.ConcurrentHashMap; + +import javax.management.timer.Timer; + +import com.microsoft.windowsazure.services.core.ServiceException; +import com.microsoft.windowsazure.services.core.utils.DateFactory; + +public class OAuthTokenManager { + private final DateFactory dateFactory; + private final URI acsBaseUri; + private final String clientId; + private final String clientSecret; + private final OAuthContract contract; + private final ConcurrentHashMap activeTokens; + + /** + * Creates an OAuth token manager instance with specified contract, date factory, ACS base URI, client ID, + * and client secret. + * + * @param contract + * A OAuthContract object instance that represents the OAUTH contract. + * + * @param dateFactory + * A DateFactory object instance that represents the date factory. + * + * @param acsBaseUri + * A URI object instance that represents the ACS base URI. + * + * @param clientId + * A String object instance that represents the client ID. + * + * @param clientSecret + * A String object instance that represents the client secret. + * + */ + public OAuthTokenManager(OAuthContract contract, DateFactory dateFactory, URI acsBaseUri, String clientId, + String clientSecret) { + this.contract = contract; + this.dateFactory = dateFactory; + this.acsBaseUri = acsBaseUri; + this.clientId = clientId; + this.clientSecret = clientSecret; + this.activeTokens = new ConcurrentHashMap(); + } + + /** + * Gets an OAuth access token with specified media service scope. + * + * @param mediaServiceScope + * A String instance that represents the media service scope. + * + * @return String + * + * @throws ServiceException + * @throws URISyntaxException + */ + public String getAccessToken(String mediaServiceScope) throws ServiceException, URISyntaxException { + Date now = dateFactory.getDate(); + OAuthTokenResponse oAuth2TokenResponse = null; + + ActiveToken activeToken = this.activeTokens.get(mediaServiceScope); + + if (activeToken != null && now.before(activeToken.getExpiresUtc())) { + return activeToken.getOAuthTokenResponse().getAccessToken(); + } + + // sweep expired tokens out of collection + Iterator> iterator = activeTokens.entrySet().iterator(); + while (iterator.hasNext()) { + Entry entry = iterator.next(); + if (!now.before(entry.getValue().getExpiresUtc())) { + iterator.remove(); + } + } + + oAuth2TokenResponse = contract.getAccessToken(acsBaseUri, clientId, clientSecret, mediaServiceScope); + + Date expiresUtc = new Date(now.getTime() + oAuth2TokenResponse.getExpiresIn() * Timer.ONE_SECOND / 2); + + ActiveToken acquiredToken = new ActiveToken(); + acquiredToken.setOAuth2TokenResponse(oAuth2TokenResponse); + acquiredToken.setExpiresUtc(expiresUtc); + this.activeTokens.put(mediaServiceScope, acquiredToken); + + return oAuth2TokenResponse.getAccessToken(); + } + +} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/media/OAuthTokenResponse.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/media/OAuthTokenResponse.java new file mode 100644 index 0000000000000..7c696ea979980 --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/media/OAuthTokenResponse.java @@ -0,0 +1,71 @@ +/** + * Copyright 2011 Microsoft Corporation + * + * Licensed 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 com.microsoft.windowsazure.services.media; + +import org.codehaus.jackson.annotate.JsonProperty; + +public class OAuthTokenResponse { + + private String _accessToken; + private String _scope; + private String _tokenType; + private long _expiresIn; + + /** + * Sets the token type. + * + * @param tokenType + */ + @JsonProperty("token_type") + public void setTokenType(String tokenType) { + _tokenType = tokenType; + } + + @JsonProperty("token_type") + public String getTokenType() { + return _tokenType; + } + + @JsonProperty("expires_in") + public long getExpiresIn() { + return _expiresIn; + } + + @JsonProperty("expires_in") + public void setExpiresIn(long expiresIn) { + _expiresIn = expiresIn; + } + + @JsonProperty("access_token") + public String getAccessToken() { + return _accessToken; + } + + @JsonProperty("access_token") + public void setAccessToken(String accessToken) { + _accessToken = accessToken; + } + + @JsonProperty("scope") + public String getScope() { + return _scope; + } + + @JsonProperty("scope") + public void setScope(String scope) { + _scope = scope; + } +} diff --git a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/media/OAuthRestProxyIntegrationTest.java b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/media/OAuthRestProxyIntegrationTest.java new file mode 100644 index 0000000000000..1b05d54794c3a --- /dev/null +++ b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/media/OAuthRestProxyIntegrationTest.java @@ -0,0 +1,55 @@ +/** + * Copyright 2011 Microsoft Corporation + * + * Licensed 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 com.microsoft.windowsazure.services.media; + +import static org.junit.Assert.*; + +import java.net.URI; + +import org.junit.Test; + +import com.microsoft.windowsazure.services.core.Configuration; +import com.sun.jersey.api.client.Client; + +public class OAuthRestProxyIntegrationTest { + @Test + public void serviceCanBeCalledToCreateAccessToken() throws Exception { + // Arrange + Configuration config = Configuration.getInstance(); + overrideWithEnv(config, MediaConfiguration.OAUTH_URI); + overrideWithEnv(config, MediaConfiguration.OAUTH_CLIENT_ID); + overrideWithEnv(config, MediaConfiguration.OAUTH_CLIENT_SECRET); + OAuthContract oAuthContract = new OAuthRestProxy(config.create(Client.class)); + + // Act + URI oAuthUri = new URI((String) config.getProperty(MediaConfiguration.OAUTH_URI)); + String clientId = (String) config.getProperty(MediaConfiguration.OAUTH_CLIENT_ID); + String clientSecret = (String) config.getProperty(MediaConfiguration.OAUTH_CLIENT_SECRET); + String scope = "urn:WindowsAzureMediaServices"; + OAuthTokenResponse result = oAuthContract.getAccessToken(oAuthUri, clientId, clientSecret, scope); + + // Assert + assertNotNull(result); + assertNotNull(result.getAccessToken()); + } + + private static void overrideWithEnv(Configuration config, String key) { + String value = System.getenv(key); + if (value == null) + return; + + config.setProperty(key, value); + } +} diff --git a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/media/OAuthTokenManagerTest.java b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/media/OAuthTokenManagerTest.java new file mode 100644 index 0000000000000..b5802a683c974 --- /dev/null +++ b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/media/OAuthTokenManagerTest.java @@ -0,0 +1,171 @@ +/** + * Copyright 2011 Microsoft Corporation + * + * Licensed 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 com.microsoft.windowsazure.services.media; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Calendar; +import java.util.Date; +import java.util.TimeZone; + +import org.codehaus.jackson.JsonParseException; +import org.codehaus.jackson.map.JsonMappingException; +import org.junit.Before; +import org.junit.Test; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +import com.microsoft.windowsazure.services.core.ServiceException; +import com.microsoft.windowsazure.services.core.utils.DateFactory; + +public class OAuthTokenManagerTest { + private OAuthContract contract; + private OAuthTokenManager client; + private DateFactory dateFactory; + private Calendar calendar; + + @Before + public void init() throws URISyntaxException { + calendar = Calendar.getInstance(TimeZone.getTimeZone("GMT")); + + dateFactory = mock(DateFactory.class); + + // Client channel = new Client(); + contract = mock(OAuthRestProxy.class); + + String acsBaseUri = "testurl"; + String accountName = "testname"; + String accountPassword = "testpassword"; + + client = new OAuthTokenManager(contract, dateFactory, new URI(acsBaseUri), accountName, accountPassword); + + when(dateFactory.getDate()).thenAnswer(new Answer() { + @Override + public Date answer(InvocationOnMock invocation) throws Throwable { + return calendar.getTime(); + } + }); + } + + private void doIncrementingTokens() throws ServiceException, URISyntaxException, JsonParseException, + JsonMappingException, IOException { + doAnswer(new Answer() { + int count = 0; + + @Override + public OAuthTokenResponse answer(InvocationOnMock invocation) throws Throwable { + ++count; + OAuthTokenResponse wrapResponse = new OAuthTokenResponse(); + wrapResponse.setAccessToken("testaccesstoken1-" + count); + wrapResponse.setExpiresIn(83); + return wrapResponse; + } + }).when(contract).getAccessToken(new URI("testurl"), "testname", "testpassword", "https://test/scope"); + + doAnswer(new Answer() { + int count = 0; + + @Override + public OAuthTokenResponse answer(InvocationOnMock invocation) throws Throwable { + ++count; + OAuthTokenResponse wrapResponse = new OAuthTokenResponse(); + wrapResponse.setAccessToken("testaccesstoken2-" + count); + wrapResponse.setExpiresIn(83); + return wrapResponse; + } + }).when(contract).getAccessToken(new URI("testurl"), "testname", "testpassword", "https://test/scope2"); + } + + @Test + public void clientUsesContractToGetToken() throws ServiceException, URISyntaxException, JsonParseException, + JsonMappingException, IOException { + // Arrange + doIncrementingTokens(); + + // Act + String accessToken = client.getAccessToken("https://test/scope"); + + // Assert + assertNotNull(accessToken); + assertEquals("testaccesstoken1-1", accessToken); + } + + @Test + public void clientWillNotCallMultipleTimesWhileAccessTokenIsValid() throws ServiceException, URISyntaxException, + JsonParseException, JsonMappingException, IOException { + // Arrange + doIncrementingTokens(); + + // Act + String accessToken1 = client.getAccessToken("https://test/scope"); + String accessToken2 = client.getAccessToken("https://test/scope"); + calendar.add(Calendar.SECOND, 40); + String accessToken3 = client.getAccessToken("https://test/scope"); + + // Assert + assertEquals("testaccesstoken1-1", accessToken1); + assertEquals("testaccesstoken1-1", accessToken2); + assertEquals("testaccesstoken1-1", accessToken3); + + verify(contract, times(1)).getAccessToken(new URI("testurl"), "testname", "testpassword", "https://test/scope"); + } + + @Test + public void callsToDifferentScopeWillResultInDifferentAccessTokens() throws ServiceException, URISyntaxException, + JsonParseException, JsonMappingException, IOException { + // Arrange + doIncrementingTokens(); + + // Act + String accessToken1 = client.getAccessToken("https://test/scope"); + String accessToken2 = client.getAccessToken("https://test/scope2"); + calendar.add(Calendar.SECOND, 40); + String accessToken3 = client.getAccessToken("https://test/scope"); + + // Assert + assertEquals("testaccesstoken1-1", accessToken1); + assertEquals("testaccesstoken2-1", accessToken2); + assertEquals("testaccesstoken1-1", accessToken3); + + verify(contract, times(1)).getAccessToken(new URI("testurl"), "testname", "testpassword", "https://test/scope"); + verify(contract, times(1)) + .getAccessToken(new URI("testurl"), "testname", "testpassword", "https://test/scope2"); + } + + @Test + public void clientWillBeCalledWhenTokenIsHalfwayToExpiring() throws ServiceException, URISyntaxException, + JsonParseException, JsonMappingException, IOException { + // Arrange + doIncrementingTokens(); + + // Act + String accessToken1 = client.getAccessToken("https://test/scope"); + String accessToken2 = client.getAccessToken("https://test/scope"); + calendar.add(Calendar.SECOND, 45); + String accessToken3 = client.getAccessToken("https://test/scope"); + + // Assert + assertEquals("testaccesstoken1-1", accessToken1); + assertEquals("testaccesstoken1-1", accessToken2); + assertEquals("testaccesstoken1-2", accessToken3); + + verify(contract, times(2)).getAccessToken(new URI("testurl"), "testname", "testpassword", "https://test/scope"); + } + +} From 1a2a15307c1462ae7c50edd7e40a248b20e03f1d Mon Sep 17 00:00:00 2001 From: Albert Cheng Date: Tue, 14 Aug 2012 18:12:07 -0700 Subject: [PATCH 2/2] address code review feedback for Authentication of nimbus SDK. --- .../services/media/ActiveToken.java | 21 ++++--- .../services/media/OAuthFilter.java | 10 +++- .../services/media/OAuthRestProxy.java | 6 ++ .../services/media/OAuthTokenManager.java | 44 ++++++-------- .../services/media/OAuthTokenResponse.java | 6 ++ .../services/media/OAuthTokenManagerTest.java | 60 +++++-------------- 6 files changed, 64 insertions(+), 83 deletions(-) diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/media/ActiveToken.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/media/ActiveToken.java index 73e1564be1f27..2be96a410507a 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/media/ActiveToken.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/media/ActiveToken.java @@ -18,7 +18,7 @@ import java.util.Date; /** - * A class representing active token for OAuthTokenResponse. + * A class representing active token. * * @author azurejava@microsoft.com * @@ -26,7 +26,7 @@ public class ActiveToken { private Date expiresUtc; - private OAuthTokenResponse oAuthTokenResponse; + private String accessToken; /** * Gets the expiration time in UTC. @@ -47,21 +47,20 @@ public void setExpiresUtc(Date expiresUtc) { } /** - * Gets the OAuth token response. + * Gets access token. * - * @return The OAuth token response. + * @return String */ - public OAuthTokenResponse getOAuthTokenResponse() { - return oAuthTokenResponse; + public String getAccessToken() { + return this.accessToken; } /** - * Sets the OAuth token response. + * Sets the access token. * - * @param oAuth2TokenResponse + * @param accessToken */ - public void setOAuth2TokenResponse(OAuthTokenResponse oAuth2TokenResponse) { - this.oAuthTokenResponse = oAuth2TokenResponse; + public void setAccessToken(String accessToken) { + this.accessToken = accessToken; } - } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/media/OAuthFilter.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/media/OAuthFilter.java index c38d53dc166c7..3fa9f48bc74ec 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/media/OAuthFilter.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/media/OAuthFilter.java @@ -22,11 +22,17 @@ import com.sun.jersey.api.client.ClientResponse; import com.sun.jersey.api.client.filter.ClientFilter; +/** + * The Jersey filter for OAuth. + * + * @author azurejava@microsoft.com + * + */ public class OAuthFilter extends ClientFilter { private final OAuthTokenManager oAuthTokenManager; /** - * Creates an OAuthFilter object with specfied OAuthTokenManager instance. + * Creates an OAuthFilter object with specified OAuthTokenManager instance. * * @param oAuthTokenManager */ @@ -45,7 +51,7 @@ public ClientResponse handle(ClientRequest clientRequest) throws ClientHandlerEx String accessToken; try { - accessToken = oAuthTokenManager.getAccessToken(clientRequest.getURI().toString()); + accessToken = oAuthTokenManager.getAccessToken(); } catch (ServiceException e) { // must wrap exception because of base class signature diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/media/OAuthRestProxy.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/media/OAuthRestProxy.java index cedae2640ffb7..9ab60ed9115ce 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/media/OAuthRestProxy.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/media/OAuthRestProxy.java @@ -34,6 +34,12 @@ import com.sun.jersey.api.client.UniformInterfaceException; import com.sun.jersey.api.representation.Form; +/** + * The OAuth rest proxy. + * + * @author azurejava@microsoft.com + * + */ public class OAuthRestProxy implements OAuthContract { Client channel; diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/media/OAuthTokenManager.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/media/OAuthTokenManager.java index 111aba7e33d20..77f2b020a2cd7 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/media/OAuthTokenManager.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/media/OAuthTokenManager.java @@ -18,22 +18,26 @@ import java.net.URI; import java.net.URISyntaxException; import java.util.Date; -import java.util.Iterator; -import java.util.Map.Entry; -import java.util.concurrent.ConcurrentHashMap; import javax.management.timer.Timer; import com.microsoft.windowsazure.services.core.ServiceException; import com.microsoft.windowsazure.services.core.utils.DateFactory; +/** + * An OAuth token manager class. + * + * @author azurejava@microsoft.com + * + */ public class OAuthTokenManager { private final DateFactory dateFactory; private final URI acsBaseUri; private final String clientId; private final String clientSecret; private final OAuthContract contract; - private final ConcurrentHashMap activeTokens; + private ActiveToken activeToken; + private final String scope; /** * Creates an OAuth token manager instance with specified contract, date factory, ACS base URI, client ID, @@ -56,13 +60,14 @@ public class OAuthTokenManager { * */ public OAuthTokenManager(OAuthContract contract, DateFactory dateFactory, URI acsBaseUri, String clientId, - String clientSecret) { + String clientSecret, String scope) { this.contract = contract; this.dateFactory = dateFactory; this.acsBaseUri = acsBaseUri; this.clientId = clientId; this.clientSecret = clientSecret; - this.activeTokens = new ConcurrentHashMap(); + this.scope = scope; + this.activeToken = null; } /** @@ -76,33 +81,22 @@ public OAuthTokenManager(OAuthContract contract, DateFactory dateFactory, URI ac * @throws ServiceException * @throws URISyntaxException */ - public String getAccessToken(String mediaServiceScope) throws ServiceException, URISyntaxException { + public String getAccessToken() throws ServiceException, URISyntaxException { Date now = dateFactory.getDate(); OAuthTokenResponse oAuth2TokenResponse = null; - ActiveToken activeToken = this.activeTokens.get(mediaServiceScope); - - if (activeToken != null && now.before(activeToken.getExpiresUtc())) { - return activeToken.getOAuthTokenResponse().getAccessToken(); + if (this.activeToken == null) { + this.activeToken = new ActiveToken(); } - - // sweep expired tokens out of collection - Iterator> iterator = activeTokens.entrySet().iterator(); - while (iterator.hasNext()) { - Entry entry = iterator.next(); - if (!now.before(entry.getValue().getExpiresUtc())) { - iterator.remove(); - } + else if (now.before(this.activeToken.getExpiresUtc())) { + return this.activeToken.getAccessToken(); } - oAuth2TokenResponse = contract.getAccessToken(acsBaseUri, clientId, clientSecret, mediaServiceScope); - + oAuth2TokenResponse = contract.getAccessToken(acsBaseUri, clientId, clientSecret, scope); Date expiresUtc = new Date(now.getTime() + oAuth2TokenResponse.getExpiresIn() * Timer.ONE_SECOND / 2); - ActiveToken acquiredToken = new ActiveToken(); - acquiredToken.setOAuth2TokenResponse(oAuth2TokenResponse); - acquiredToken.setExpiresUtc(expiresUtc); - this.activeTokens.put(mediaServiceScope, acquiredToken); + this.activeToken.setAccessToken(oAuth2TokenResponse.getAccessToken()); + this.activeToken.setExpiresUtc(expiresUtc); return oAuth2TokenResponse.getAccessToken(); } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/media/OAuthTokenResponse.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/media/OAuthTokenResponse.java index 7c696ea979980..95a9dae61f674 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/media/OAuthTokenResponse.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/media/OAuthTokenResponse.java @@ -17,6 +17,12 @@ import org.codehaus.jackson.annotate.JsonProperty; +/** + * A class representing OAuth token response. + * + * @author azurejava@microsoft.com + * + */ public class OAuthTokenResponse { private String _accessToken; diff --git a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/media/OAuthTokenManagerTest.java b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/media/OAuthTokenManagerTest.java index b5802a683c974..241d2ecc91e57 100644 --- a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/media/OAuthTokenManagerTest.java +++ b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/media/OAuthTokenManagerTest.java @@ -52,8 +52,9 @@ public void init() throws URISyntaxException { String acsBaseUri = "testurl"; String accountName = "testname"; String accountPassword = "testpassword"; + String scope = "urn:WindowsAzureMediaServices"; - client = new OAuthTokenManager(contract, dateFactory, new URI(acsBaseUri), accountName, accountPassword); + client = new OAuthTokenManager(contract, dateFactory, new URI(acsBaseUri), accountName, accountPassword, scope); when(dateFactory.getDate()).thenAnswer(new Answer() { @Override @@ -76,20 +77,9 @@ public OAuthTokenResponse answer(InvocationOnMock invocation) throws Throwable { wrapResponse.setExpiresIn(83); return wrapResponse; } - }).when(contract).getAccessToken(new URI("testurl"), "testname", "testpassword", "https://test/scope"); + }).when(contract).getAccessToken(new URI("testurl"), "testname", "testpassword", + "urn:WindowsAzureMediaServices"); - doAnswer(new Answer() { - int count = 0; - - @Override - public OAuthTokenResponse answer(InvocationOnMock invocation) throws Throwable { - ++count; - OAuthTokenResponse wrapResponse = new OAuthTokenResponse(); - wrapResponse.setAccessToken("testaccesstoken2-" + count); - wrapResponse.setExpiresIn(83); - return wrapResponse; - } - }).when(contract).getAccessToken(new URI("testurl"), "testname", "testpassword", "https://test/scope2"); } @Test @@ -99,7 +89,7 @@ public void clientUsesContractToGetToken() throws ServiceException, URISyntaxExc doIncrementingTokens(); // Act - String accessToken = client.getAccessToken("https://test/scope"); + String accessToken = client.getAccessToken(); // Assert assertNotNull(accessToken); @@ -113,39 +103,18 @@ public void clientWillNotCallMultipleTimesWhileAccessTokenIsValid() throws Servi doIncrementingTokens(); // Act - String accessToken1 = client.getAccessToken("https://test/scope"); - String accessToken2 = client.getAccessToken("https://test/scope"); + String accessToken1 = client.getAccessToken(); + String accessToken2 = client.getAccessToken(); calendar.add(Calendar.SECOND, 40); - String accessToken3 = client.getAccessToken("https://test/scope"); + String accessToken3 = client.getAccessToken(); // Assert assertEquals("testaccesstoken1-1", accessToken1); assertEquals("testaccesstoken1-1", accessToken2); assertEquals("testaccesstoken1-1", accessToken3); - verify(contract, times(1)).getAccessToken(new URI("testurl"), "testname", "testpassword", "https://test/scope"); - } - - @Test - public void callsToDifferentScopeWillResultInDifferentAccessTokens() throws ServiceException, URISyntaxException, - JsonParseException, JsonMappingException, IOException { - // Arrange - doIncrementingTokens(); - - // Act - String accessToken1 = client.getAccessToken("https://test/scope"); - String accessToken2 = client.getAccessToken("https://test/scope2"); - calendar.add(Calendar.SECOND, 40); - String accessToken3 = client.getAccessToken("https://test/scope"); - - // Assert - assertEquals("testaccesstoken1-1", accessToken1); - assertEquals("testaccesstoken2-1", accessToken2); - assertEquals("testaccesstoken1-1", accessToken3); - - verify(contract, times(1)).getAccessToken(new URI("testurl"), "testname", "testpassword", "https://test/scope"); - verify(contract, times(1)) - .getAccessToken(new URI("testurl"), "testname", "testpassword", "https://test/scope2"); + verify(contract, times(1)).getAccessToken(new URI("testurl"), "testname", "testpassword", + "urn:WindowsAzureMediaServices"); } @Test @@ -155,17 +124,18 @@ public void clientWillBeCalledWhenTokenIsHalfwayToExpiring() throws ServiceExcep doIncrementingTokens(); // Act - String accessToken1 = client.getAccessToken("https://test/scope"); - String accessToken2 = client.getAccessToken("https://test/scope"); + String accessToken1 = client.getAccessToken(); + String accessToken2 = client.getAccessToken(); calendar.add(Calendar.SECOND, 45); - String accessToken3 = client.getAccessToken("https://test/scope"); + String accessToken3 = client.getAccessToken(); // Assert assertEquals("testaccesstoken1-1", accessToken1); assertEquals("testaccesstoken1-1", accessToken2); assertEquals("testaccesstoken1-2", accessToken3); - verify(contract, times(2)).getAccessToken(new URI("testurl"), "testname", "testpassword", "https://test/scope"); + verify(contract, times(2)).getAccessToken(new URI("testurl"), "testname", "testpassword", + "urn:WindowsAzureMediaServices"); } }