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"); + } + +}