diff --git a/src/main/java/microsoft/exchange/webservices/data/credential/BearerTokenCredentials.java b/src/main/java/microsoft/exchange/webservices/data/credential/BearerTokenCredentials.java new file mode 100644 index 000000000..679ce8e92 --- /dev/null +++ b/src/main/java/microsoft/exchange/webservices/data/credential/BearerTokenCredentials.java @@ -0,0 +1,90 @@ +/* + * The MIT License Copyright (c) 2017 Kevin Burek + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated + * documentation files (the "Software"), to deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package microsoft.exchange.webservices.data.credential; + +import java.util.Map; + +import microsoft.exchange.webservices.data.core.request.HttpWebRequest; +import microsoft.exchange.webservices.data.credential.ExchangeCredentials; + +/** + * BearerTokenCredentials is used for OAuth2 bearer-token credentials. https://tools.ietf.org/html/rfc6750 + */ +public class BearerTokenCredentials extends ExchangeCredentials { + + /** + * Bearer token format regular expression. https://tools.ietf.org/html/rfc6750#section-2.1 + */ + private static final String BEARER_TOKEN_FORMAT_REGEX = "^[-._~+/A-Za-z0-9]+=*$"; + + private static final String AUTHORIZATION = "Authorization"; + + private static final String BEARER_AUTH_PREAMBLE = "Bearer "; + + /** + * The domain. + */ + private String token; + + /** + * Gets the token string. + * + * @return the token. + */ + public String getToken() { + return token; + } + + /** + * Initializes a new instance to specified token string. + */ + public BearerTokenCredentials(String bearerToken) { + if (bearerToken == null) { + throw new IllegalArgumentException("Bearer token can not be null"); + } + + this.validateToken(bearerToken); + + this.token = bearerToken; + } + + /** + * Validates the format of the bearer token, per RFC 6750. + * + * @param bearerToken The token string. + * @throws IllegalArgumentException When the token fails validation. + */ + protected void validateToken(String bearerToken) throws IllegalArgumentException { + if (!bearerToken.matches(BEARER_TOKEN_FORMAT_REGEX)) { + throw new IllegalArgumentException("Bearer token format is invalid."); + } + } + + /** + * This method is called to apply credential to a service request before the request is made. + * + * @param request The request. + */ + @Override + public void prepareWebRequest(HttpWebRequest request) { + Map headersMap = request.getHeaders(); + String bearerValue = BEARER_AUTH_PREAMBLE + token; + headersMap.put(AUTHORIZATION, bearerValue); + request.setHeaders(headersMap); + } +} diff --git a/src/test/java/microsoft/exchange/webservices/data/credential/BearerTokenCredentialsTest.java b/src/test/java/microsoft/exchange/webservices/data/credential/BearerTokenCredentialsTest.java new file mode 100644 index 000000000..574cae951 --- /dev/null +++ b/src/test/java/microsoft/exchange/webservices/data/credential/BearerTokenCredentialsTest.java @@ -0,0 +1,111 @@ +/* + * The MIT License + * Copyright (c) 2012 Microsoft Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package microsoft.exchange.webservices.data.credential; + +import static org.hamcrest.CoreMatchers.allOf; +import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.core.StringContains.containsString; +import static org.hamcrest.text.IsEmptyString.isEmptyOrNullString; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import microsoft.exchange.webservices.data.core.EwsUtilities; +import microsoft.exchange.webservices.data.core.EwsXmlReader; +import microsoft.exchange.webservices.data.core.request.HttpWebRequest; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +import javax.xml.stream.XMLOutputFactory; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamWriter; +import javax.xml.stream.events.Characters; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.StringWriter; +import java.io.Writer; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +@RunWith(JUnit4.class) public class BearerTokenCredentialsTest { + + private static final Log LOG = LogFactory.getLog(BearerTokenCredentialsTest.class); + + private static final String SUPERFICIALLY_VALID_TOKEN = "Zm9vLmJhcg=="; + + @Mock HttpWebRequest webRequest; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + } + + @Test public void testEmitBearerAuthorizationHeader() throws URISyntaxException { + + final String authorizationHeaderKey = "Authorization"; + final String bearerTokenPrefix = "Bearer"; + final String anyValidToken = SUPERFICIALLY_VALID_TOKEN; + HttpWebRequest mockRequest = mock(HttpWebRequest.class); + + final ArrayList> headersContainer = new ArrayList>(); + headersContainer.add(new HashMap()); + when(mockRequest.getHeaders()).thenReturn(headersContainer.get(0)); + doAnswer(new Answer() { + @Override + public Object answer(InvocationOnMock invocation) throws Throwable { + headersContainer.set(0, (HashMap) invocation.getArguments()[0]); + return null; + } + }).when(mockRequest).setHeaders((Map) any()); + + ExchangeCredentials creds = new BearerTokenCredentials(anyValidToken); + + creds.prepareWebRequest(mockRequest); + + Map actualHeaders = mockRequest.getHeaders(); + Assert.assertTrue("Headers must contain authenticate line.", + actualHeaders.containsKey(authorizationHeaderKey)); + String actualAuthorizationHeader = actualHeaders.get(authorizationHeaderKey); + Assert.assertTrue("Header value must start with " + bearerTokenPrefix, + actualAuthorizationHeader.startsWith(bearerTokenPrefix)); + Assert.assertTrue("Header value must contain token string.", + actualAuthorizationHeader.contains(anyValidToken)); + } + +}