├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── build.gradle ├── settings.gradle └── src ├── main └── java │ └── com │ └── capitalone │ └── auth │ ├── ClientCredentialsProvider.java │ ├── Token.java │ ├── TokenService.java │ ├── example │ └── ExampleClient.java │ └── oauth │ ├── exceptions │ ├── LockInterruptedException.java │ └── SSLContextException.java │ ├── factory │ ├── HttpConnectionConfig.java │ ├── HttpConnectionFactory.java │ ├── HttpConnectionFactoryImpl.java │ └── HttpConnectionPool.java │ ├── framework │ ├── ClientCredentialsNotFoundException.java │ ├── OAuthClientCredentials.java │ ├── OAuthClientCredentialsProvider.java │ └── protocol │ │ └── ServerOAuthToken.java │ └── service │ ├── ClientSecretException.java │ ├── ClientSecretService.java │ ├── OAuthToken.java │ ├── OAuthTokenAttributes.java │ └── OAuthTokenService.java └── test └── java └── com └── capitalone └── auth └── oauth ├── factory └── HttpConnectionFactoryImplTest.java ├── framework └── OAuthClientCredentialsProviderTest.java └── service └── OAuthTokenServiceTest.java /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .gradle 3 | build 4 | out 5 | *.ipr 6 | *.iws 7 | *.iml -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 1. Definitions. 7 | "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. 8 | "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. 9 | "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. 10 | "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. 11 | "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. 12 | "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. 13 | "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). 14 | "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. 15 | "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." 16 | "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 17 | 18 | 2. Grant of Copyright License. 19 | Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 20 | 21 | 3. Grant of Patent License. 22 | Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 23 | 24 | 4. Redistribution. 25 | You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: 26 | You must give any other recipients of the Work or Derivative Works a copy of this License; and 27 | You must cause any modified files to carry prominent notices stating that You changed the files; and 28 | You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and 29 | If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License.  30 | 31 | You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 32 | 33 | 5. Submission of Contributions. 34 | Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 35 | 36 | 6. Trademarks. 37 | This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 38 | 39 | 7. Disclaimer of Warranty. 40 | Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 41 | 42 | 8. Limitation of Liability. 43 | In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 44 | 45 | 9. Accepting Warranty or Additional Liability. 46 | While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. 47 | 48 | END OF TERMS AND CONDITIONS 49 | 50 | APPENDIX: HOW TO APPLY THE APACHE LICENSE TO YOUR WORK 51 | 52 | To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. 53 | 54 | Copyright [YYYY] [name of copyright owner] 55 | 56 | Licensed under the Apache License, Version 2.0 (the "License"); 57 | you may not use this file except in compliance with the License. 58 | You may obtain a copy of the License at 59 | 60 | http://www.apache.org/licenses/LICENSE-2.0 61 | 62 | Unless required by applicable law or agreed to in writing, software 63 | distributed under the License is distributed on an "AS IS" BASIS, 64 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 65 | See the License for the specific language governing permissions and 66 | limitations under the License. 67 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ** Capital One built this project to help our engineers as well as users in the community. We are no longer able to fully support the project. We have archived the project as of Jul 9 2019 where it will be available in a read-only state. Feel free to fork the project and maintain your own version. ** 2 | 3 | [![License](https://img.shields.io/badge/license-Apache%202-blue.svg)](https://www.apache.org/licenses/LICENSE-2.0) 4 | 5 | # Java Auth Client Library 6 | 7 | # Table of Contents 8 | 9 | 1. [Links](#links) 10 | 2. [Overview](#overview) 11 | 1. [Obtaining OAuth Token](#obtaining-the-oauth-token) 12 | 2. [Maintaining OAuth Token Lifecycle](#maintaining-oauth-token-lifecycle) 13 | 3. [Example Usage](#example-usage) 14 | 4. [Contributors](#contributors) 15 | 5. [Copyright](#copyright) 16 | 17 | ## Overview 18 | Supports OAuth authentication and provides interfaces to add more authentication methods. 19 | 20 | Conceptually, the library has two parts: 21 | 1. Obtaining the OAuth token 22 | 2. Maintaining OAuth token lifecycle 23 | 24 | ### Obtaining the OAuth token 25 | `OAuthClientCredentials` is at the base of obtaining a token. It holds the information about the client requesting the app. This is: 26 | 1. `grantType` (Type of grant that is being requested) 27 | 2. `clientId` (ID of your app) 28 | 3. `clientSecret` (Secret of your app) 29 | 30 | This class also holds other bits of information like the URL of the authorisation server (`authURI`) and the regular expression pattern (`clientURIRegex`) to match the client URIs needing the authorisation. 31 | 32 | Suppose you have two URIs - one http://awesomeserver.com/hello and http://coolserver.com/hello being the other. If both of these require authorisation from the same server and if that authorisation can be fulfilled with the same set of client credentials, then you only need one instance of `OAuthClientCredentials` class but with a regular expression in clientURIRegex that can match both of those URIs. Howeever, if both of those URIs require different set of client credentials or a different authorisation server or both, then you need two separate instances. 33 | 34 | Once you have one or more instances of `OAuthClientCredentials` class, create an instance of `ClientCredentialsProvider` class with type `OAuthClientCredentials`. This class needs at least one instance of the `OAuthClientCredentials` and thus requires it to be passed in the constructor. 35 | 36 | The `ClientCredentialsProvider`, as the name suggests provides the credentials upon request. When `getClientCredentialsFor` is invoked with a URI, it loops through all of its `OAuthClientCredentials` objects and runs the `clientURIRegex` match against the given URI. It returns the first `OAuthClientCredentials` that matches the URI. 37 | 38 | Once you have a working instance of `ClientCredentialsProvider`, create an instance of OAuthTokenService. The constructor needs the following: 39 | 1. `httpConnectionFactory` (Factory generating your HTTP connections) 40 | 2. `httpConnectionConfig` (Configuration for your HTTP connections. This includes sslProtocol, which defaults to TLSv1.2) 41 | 3. `prefetchPoolSize` (Size of your prefetch pool - more on this later) 42 | 4. `oAuthClientCredentialsProvider` (Your OAuthClientCredentials provider instance) 43 | 44 | When requesting the token for the first time (using `obtainTokenFor`), the service simply returns the `OAuthToken` object as `Token`. 45 | 46 | ### Maintaining OAuth token lifecycle 47 | The `Token` object is cached as soon as it is returned for the first time. When a request comes for the next time and if the token has not expired, it simply returns the cached token. 48 | 49 | However, if the token has certain amount of time left (defined in `prefetchTimeout`) before expiry, the token service fires off a prefetch job which asynchronously updates the token. All requests coming in during this time period use the token that has been cached and is about to expire. Once the asynchronous job returns a valid token, the current token is replaced with that token which is then returned to all subsequent requests. 50 | 51 | If the requests have slowed down and the `OAuthTokenService` didn't get a chance to update the token asynchronously, it simply blocks the current request thread and gets the token synchronously (which it then caches). 52 | 53 | ## Example Usage 54 | Include the following in your gradle file. Make sure you replace $version what whatever version of the library you want to use. 55 | ```groovy 56 | dependencies { 57 | compile 'com.capitalone.util:c1-oauth-client-java:$version' 58 | } 59 | ``` 60 | 61 | Here's an example client application using the library in action: 62 | ```java 63 | package com.capitalone.auth.example; 64 | 65 | import com.capitalone.auth.ClientCredentialsProvider; 66 | import com.capitalone.auth.TokenService; 67 | import com.capitalone.auth.oauth.factory.HttpConnectionConfig; 68 | import com.capitalone.auth.oauth.factory.HttpConnectionFactory; 69 | import com.capitalone.auth.oauth.factory.HttpConnectionFactoryImpl; 70 | import com.capitalone.auth.oauth.framework.OAuthClientCredentials; 71 | import com.capitalone.auth.oauth.framework.OAuthClientCredentialsProvider; 72 | import com.capitalone.auth.oauth.service.ClientSecretException; 73 | import com.capitalone.auth.oauth.service.ClientSecretService; 74 | import com.capitalone.auth.oauth.service.OAuthToken; 75 | import com.capitalone.auth.oauth.service.OAuthTokenService; 76 | 77 | import java.io.IOException; 78 | import java.net.URI; 79 | import java.net.URISyntaxException; 80 | import java.util.concurrent.locks.ReentrantLock; 81 | 82 | public class ExampleClient { 83 | 84 | private final TokenService oAuthTokenService; 85 | 86 | public static void main(String[] args) { 87 | ExampleClient client = new ExampleClient(); 88 | client.makeRequest(); 89 | } 90 | 91 | public ExampleClient() { 92 | URI authServerURI = null; 93 | 94 | try { 95 | authServerURI = new URI("https://myoauthserver.com/oauth/oauth20/token"); 96 | } catch (URISyntaxException e) { 97 | e.printStackTrace(); 98 | } 99 | 100 | OAuthClientCredentials builtClientCredentials = OAuthClientCredentials.newBuilder() 101 | .clientId("my_client_id") 102 | .clientSecret("my_client_secret") 103 | .clientSecretEncryptionKey(null) 104 | .clientURIRegex(".*") 105 | .grantType("client_credentials") 106 | .authServerURI(authServerURI) 107 | .build(); 108 | 109 | ClientCredentialsProvider clientCredentialsProvider = new OAuthClientCredentialsProvider(builtClientCredentials); 110 | 111 | ClientSecretService clientSecretService = new DummyClientSecretService(); 112 | 113 | HttpConnectionFactory httpConnectionFactory = new HttpConnectionFactoryImpl(new ReentrantLock()); 114 | 115 | HttpConnectionConfig httpConnectionConfig = HttpConnectionConfig.newBuilder() 116 | .httpConnectionTimeout(60000) 117 | .httpSocketTimeout(40000) 118 | .maxHttpConnections(20) 119 | .sslProtocol("TLSv1.2") 120 | .build(); 121 | 122 | oAuthTokenService = new OAuthTokenService(httpConnectionFactory, httpConnectionConfig, 20, 10, clientCredentialsProvider, clientSecretService); 123 | } 124 | 125 | public void makeRequest() { 126 | try { 127 | OAuthToken token = (OAuthToken) oAuthTokenService.obtainTokenFor(new URI("https://myoauthserver.com/partners/sparkpost/transmissions")); 128 | System.out.println(token.getValue()); 129 | } catch (IOException | URISyntaxException e) { 130 | e.printStackTrace(); 131 | } 132 | } 133 | 134 | private class DummyClientSecretService implements ClientSecretService { 135 | @Override 136 | public String obtainClientSecret(OAuthClientCredentials clientCredentials) throws ClientSecretException { 137 | if (null == clientCredentials.getClientSecretEncryptionKey()) { 138 | return clientCredentials.getClientSecret(); 139 | } else { 140 | return decryptSecret(clientCredentials.getClientSecretEncryptionKey(), clientCredentials.getClientSecret()); 141 | } 142 | } 143 | 144 | private String decryptSecret(String encryptionKey, String encryptedClientSecret) { 145 | return "abc1"; 146 | } 147 | } 148 | } 149 | ``` 150 | 151 | ## Dependencies 152 | | Library | Version | License | 153 | | ---------------------------------------------- | ------- | -------------------- | 154 | | commons-io:commons-io | 2.4 | Apache 2.0 | 155 | | org.apache.httpcomponents:httpclient | 4.5.2 | Apache 2.0 | 156 | | com.fasterxml.jackson.core:jackson-databind | 2.3.4 | Apache 2.0 | 157 | | commons-lang:commons-lang | 2.6 | Apache 2.0 | 158 | | junit:junit | 4.11 | CAPL 1.0, CPL 1.0 | 159 | | org.mockito:mockito-core | 1.10.19 | MIT | 160 | | org.hamcrest:hamcrest-all | 1.3 | BSD 2 -clause | 161 | 162 | 163 | ## Contributors 164 | We welcome your interest in Capital One's Open Source Projects (the "Project"). Any Contributor to the project must accept and sign a CLA indicating agreement to the license terms. Except for the license granted in this CLA to Capital One and to recipients of software distributed by Capital One, you reserve all right, title, and interest in and to your contributions; this CLA does not impact your rights to use your own contributions for any other purpose. 165 | 166 | ##### [Link to CLA](https://docs.google.com/forms/d/19LpBBjykHPox18vrZvBbZUcK6gQTj7qv1O5hCduAZFU/viewform) 167 | ##### [Link to Corporate Agreement](https://docs.google.com/forms/d/e/1FAIpQLSeAbobIPLCVZD_ccgtMWBDAcN68oqbAJBQyDTSAQ1AkYuCp_g/viewform?usp=send_form) 168 | This project adheres to the [Open Source Code of Conduct][code-of-conduct]. By participating, you are expected to honor this code. 169 | 170 | [code-of-conduct]: https://developer.capitalone.com/single/code-of-conduct/ 171 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright [2016] Capital One Services, LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and limitations under the License. 14 | */ 15 | 16 | apply plugin: 'java' 17 | apply plugin: 'eclipse' 18 | apply plugin: 'idea' 19 | apply plugin: 'jacoco' 20 | 21 | group = 'com.capitalone.util' 22 | version = '0.1-SNAPSHOT' 23 | 24 | description = 'Library to get authorisation from an OAuth server.' 25 | sourceCompatibility = 1.7 26 | targetCompatibility = 1.7 27 | 28 | jacoco { 29 | toolVersion = "0.7.1.201405082137" 30 | reportsDir = file("$buildDir/customJacocoReportDir") 31 | } 32 | 33 | jacocoTestReport { 34 | afterEvaluate { 35 | classDirectories = files(classDirectories.files.collect { 36 | fileTree( 37 | dir: it, 38 | exclude: []) 39 | } 40 | ) 41 | } 42 | 43 | reports { 44 | xml.enabled false 45 | csv.enabled false 46 | html.destination "${buildDir}/jacocoHtml" 47 | } 48 | } 49 | 50 | repositories { 51 | mavenCentral() 52 | } 53 | 54 | dependencies { 55 | compile 'commons-io:commons-io:2.4' 56 | compile 'org.apache.httpcomponents:httpclient:4.5.2' 57 | compile 'com.fasterxml.jackson.core:jackson-databind:2.3.4' 58 | compile group: 'commons-lang', name: 'commons-lang', version: '2.6' 59 | 60 | // Testing dependencies 61 | testCompile("junit:junit:4.11") 62 | testCompile("org.mockito:mockito-core:1.10.19") { 63 | exclude module: 'org.hamcrest:hamcrest-core' 64 | } 65 | testCompile("org.hamcrest:hamcrest-all:1.3") 66 | } 67 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright [2016] Capital One Services, LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and limitations under the License. 14 | */ 15 | rootProject.name = 'c1-oauth-client-java' -------------------------------------------------------------------------------- /src/main/java/com/capitalone/auth/ClientCredentialsProvider.java: -------------------------------------------------------------------------------- 1 | package com.capitalone.auth; 2 | 3 | import com.capitalone.auth.oauth.framework.ClientCredentialsNotFoundException; 4 | 5 | import java.net.URI; 6 | 7 | /** 8 | * Copyright [2016] Capital One Services, LLC 9 | * 10 | * Licensed under the Apache License, Version 2.0 (the "License"); 11 | * you may not use this file except in compliance with the License. 12 | * You may obtain a copy of the License at 13 | * 14 | * http://www.apache.org/licenses/LICENSE-2.0 15 | * 16 | * Unless required by applicable law or agreed to in writing, software 17 | * distributed under the License is distributed on an "AS IS" BASIS, 18 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | * See the License for the specific language governing permissions and limitations under the License. 20 | */ 21 | public interface ClientCredentialsProvider { 22 | T getClientCredentialsFor(URI any) throws ClientCredentialsNotFoundException; 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/capitalone/auth/Token.java: -------------------------------------------------------------------------------- 1 | package com.capitalone.auth; 2 | 3 | /** 4 | * Copyright [2016] Capital One Services, LLC 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and limitations under the License. 16 | */ 17 | public interface Token { 18 | String getValue(); 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/capitalone/auth/TokenService.java: -------------------------------------------------------------------------------- 1 | package com.capitalone.auth; 2 | 3 | import java.io.IOException; 4 | import java.net.URI; 5 | 6 | /** 7 | * Copyright [2016] Capital One Services, LLC 8 | * 9 | * Licensed under the Apache License, Version 2.0 (the "License"); 10 | * you may not use this file except in compliance with the License. 11 | * You may obtain a copy of the License at 12 | * 13 | * http://www.apache.org/licenses/LICENSE-2.0 14 | * 15 | * Unless required by applicable law or agreed to in writing, software 16 | * distributed under the License is distributed on an "AS IS" BASIS, 17 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18 | * See the License for the specific language governing permissions and limitations under the License. 19 | */ 20 | public interface TokenService { 21 | 22 | /** 23 | * @param uri 24 | * @return 25 | */ 26 | Token obtainTokenFor(URI uri) throws IOException; 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/capitalone/auth/example/ExampleClient.java: -------------------------------------------------------------------------------- 1 | package com.capitalone.auth.example; 2 | 3 | import com.capitalone.auth.ClientCredentialsProvider; 4 | import com.capitalone.auth.TokenService; 5 | import com.capitalone.auth.oauth.factory.HttpConnectionConfig; 6 | import com.capitalone.auth.oauth.factory.HttpConnectionFactory; 7 | import com.capitalone.auth.oauth.factory.HttpConnectionFactoryImpl; 8 | import com.capitalone.auth.oauth.framework.OAuthClientCredentials; 9 | import com.capitalone.auth.oauth.framework.OAuthClientCredentialsProvider; 10 | import com.capitalone.auth.oauth.service.ClientSecretException; 11 | import com.capitalone.auth.oauth.service.ClientSecretService; 12 | import com.capitalone.auth.oauth.service.OAuthToken; 13 | import com.capitalone.auth.oauth.service.OAuthTokenService; 14 | 15 | import java.io.IOException; 16 | import java.net.URI; 17 | import java.net.URISyntaxException; 18 | import java.util.concurrent.locks.ReentrantLock; 19 | 20 | /** 21 | * Copyright [2016] Capital One Services, LLC 22 | * 23 | * Licensed under the Apache License, Version 2.0 (the "License"); 24 | * you may not use this file except in compliance with the License. 25 | * You may obtain a copy of the License at 26 | * 27 | * http://www.apache.org/licenses/LICENSE-2.0 28 | * 29 | * Unless required by applicable law or agreed to in writing, software 30 | * distributed under the License is distributed on an "AS IS" BASIS, 31 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 32 | * See the License for the specific language governing permissions and limitations under the License. 33 | */ 34 | public class ExampleClient { 35 | 36 | private final TokenService oAuthTokenService; 37 | 38 | public static void main(String[] args) { 39 | ExampleClient client = new ExampleClient(); 40 | client.makeRequest(); 41 | } 42 | 43 | public ExampleClient() { 44 | URI authServerURI = null; 45 | 46 | try { 47 | authServerURI = new URI("https://myauthserver.com/oauth/oauth20/token"); 48 | } catch (URISyntaxException e) { 49 | e.printStackTrace(); 50 | } 51 | 52 | OAuthClientCredentials builtClientCredentials = OAuthClientCredentials.newBuilder() 53 | .clientId("my_client_id") 54 | .clientSecret("my_client_secret") 55 | .clientSecretEncryptionKey(null) 56 | .clientURIRegex(".*") 57 | .grantType("client_credentials") 58 | .authServerURI(authServerURI) 59 | .build(); 60 | 61 | ClientCredentialsProvider clientCredentialsProvider = new OAuthClientCredentialsProvider(builtClientCredentials); 62 | 63 | ClientSecretService clientSecretService = new DummyClientSecretService(); 64 | 65 | HttpConnectionFactory httpConnectionFactory = new HttpConnectionFactoryImpl(new ReentrantLock()); 66 | 67 | HttpConnectionConfig httpConnectionConfig = HttpConnectionConfig.newBuilder() 68 | .httpConnectionTimeout(60000) 69 | .httpSocketTimeout(40000) 70 | .maxHttpConnections(20) 71 | .build(); 72 | 73 | oAuthTokenService = new OAuthTokenService(httpConnectionFactory, httpConnectionConfig, 20, 10, clientCredentialsProvider, clientSecretService); 74 | } 75 | 76 | public void makeRequest() { 77 | try { 78 | OAuthToken token = (OAuthToken) oAuthTokenService.obtainTokenFor(new URI("https://myauthserver.com/protected/endpoint")); 79 | System.out.println(token.getValue()); 80 | } catch (IOException | URISyntaxException e) { 81 | e.printStackTrace(); 82 | } 83 | } 84 | 85 | private class DummyClientSecretService implements ClientSecretService { 86 | @Override 87 | public String obtainClientSecret(OAuthClientCredentials clientCredentials) throws ClientSecretException { 88 | if (null == clientCredentials.getClientSecretEncryptionKey()) { 89 | return clientCredentials.getClientSecret(); 90 | } else { 91 | return decryptSecret(clientCredentials.getClientSecretEncryptionKey(), clientCredentials.getClientSecret()); 92 | } 93 | } 94 | 95 | private String decryptSecret(String encryptionKey, String encryptedClientSecret) { 96 | return "abc1"; 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/main/java/com/capitalone/auth/oauth/exceptions/LockInterruptedException.java: -------------------------------------------------------------------------------- 1 | package com.capitalone.auth.oauth.exceptions; 2 | 3 | /** 4 | * Copyright [2016] Capital One Services, LLC 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and limitations under the License. 16 | */ 17 | public class LockInterruptedException extends RuntimeException { 18 | 19 | public LockInterruptedException(String message, Throwable throwable) { 20 | super(message, throwable); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/capitalone/auth/oauth/exceptions/SSLContextException.java: -------------------------------------------------------------------------------- 1 | package com.capitalone.auth.oauth.exceptions; 2 | 3 | /** 4 | * Copyright [2016] Capital One Services, LLC 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and limitations under the License. 16 | */ 17 | public class SSLContextException extends RuntimeException { 18 | 19 | public SSLContextException(String message, Throwable throwable) { 20 | super(message, throwable); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/capitalone/auth/oauth/factory/HttpConnectionConfig.java: -------------------------------------------------------------------------------- 1 | package com.capitalone.auth.oauth.factory; 2 | 3 | /** 4 | * Copyright [2016] Capital One Services, LLC 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and limitations under the License. 16 | */ 17 | public class HttpConnectionConfig { 18 | private static final String DEFAULT_SSL_PROTOCOL = "TLSv1.2"; 19 | 20 | private final Integer httpConnectionTimeout; 21 | private final Integer httpSocketTimeout; 22 | private final Integer maxHttpConnections; 23 | private final String sslProtocol; 24 | 25 | private HttpConnectionConfig(final Builder builder) { 26 | this.httpConnectionTimeout = builder.httpConnectionTimeout; 27 | this.httpSocketTimeout = builder.httpSocketTimeout; 28 | this.maxHttpConnections = builder.maxHttpConnections; 29 | this.sslProtocol = builder.sslProtocol; 30 | } 31 | 32 | public static Builder newBuilder() { 33 | return new Builder(); 34 | } 35 | 36 | public Integer getHttpConnectionTimeout() { 37 | return httpConnectionTimeout; 38 | } 39 | 40 | public Integer getHttpSocketTimeout() { 41 | return httpSocketTimeout; 42 | } 43 | 44 | public Integer getMaxHttpConnections() { 45 | return maxHttpConnections; 46 | } 47 | 48 | public String getSslProtocol() { 49 | return sslProtocol; 50 | } 51 | 52 | @Override 53 | public boolean equals(Object o) { 54 | if (this == o) { 55 | return true; 56 | } 57 | if (o == null || getClass() != o.getClass()) { 58 | return false; 59 | } 60 | 61 | HttpConnectionConfig that = (HttpConnectionConfig) o; 62 | 63 | if (httpConnectionTimeout != null ? !httpConnectionTimeout.equals(that.httpConnectionTimeout) : that.httpConnectionTimeout != null) { 64 | return false; 65 | } 66 | if (httpSocketTimeout != null ? !httpSocketTimeout.equals(that.httpSocketTimeout) : that.httpSocketTimeout != null) { 67 | return false; 68 | } 69 | if (maxHttpConnections != null ? !maxHttpConnections.equals(that.maxHttpConnections) : that.maxHttpConnections != null) { 70 | return false; 71 | } 72 | if (sslProtocol != null ? !sslProtocol.equals(that.sslProtocol) : that.sslProtocol != null) { 73 | return false; 74 | } 75 | 76 | return true; 77 | } 78 | 79 | @Override 80 | public int hashCode() { 81 | int result = httpConnectionTimeout != null ? httpConnectionTimeout.hashCode() : 0; 82 | result = 31 * result + (httpSocketTimeout != null ? httpSocketTimeout.hashCode() : 0); 83 | result = 31 * result + (maxHttpConnections != null ? maxHttpConnections.hashCode() : 0); 84 | result = 31 * result + (sslProtocol != null ? sslProtocol.hashCode() : 0); 85 | return result; 86 | } 87 | 88 | public static final class Builder { 89 | private Integer httpConnectionTimeout; 90 | private Integer httpSocketTimeout; 91 | private Integer maxHttpConnections; 92 | private String sslProtocol = DEFAULT_SSL_PROTOCOL; 93 | 94 | private Builder() { 95 | } 96 | 97 | public Builder httpConnectionTimeout(Integer val) { 98 | httpConnectionTimeout = val; 99 | return this; 100 | } 101 | 102 | public Builder httpSocketTimeout(Integer val) { 103 | httpSocketTimeout = val; 104 | return this; 105 | } 106 | 107 | public Builder maxHttpConnections(Integer val) { 108 | maxHttpConnections = val; 109 | return this; 110 | } 111 | 112 | public Builder sslProtocol(String sslProtocol) { 113 | this.sslProtocol = sslProtocol; 114 | return this; 115 | } 116 | 117 | public HttpConnectionConfig build() { 118 | return new HttpConnectionConfig(this); 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/main/java/com/capitalone/auth/oauth/factory/HttpConnectionFactory.java: -------------------------------------------------------------------------------- 1 | package com.capitalone.auth.oauth.factory; 2 | 3 | /** 4 | * Copyright [2016] Capital One Services, LLC 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and limitations under the License. 16 | */ 17 | public interface HttpConnectionFactory { 18 | HttpConnectionPool getConnectionPool(HttpConnectionConfig params); 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/capitalone/auth/oauth/factory/HttpConnectionFactoryImpl.java: -------------------------------------------------------------------------------- 1 | package com.capitalone.auth.oauth.factory; 2 | 3 | import com.capitalone.auth.oauth.exceptions.LockInterruptedException; 4 | import com.capitalone.auth.oauth.exceptions.SSLContextException; 5 | import org.apache.http.config.Registry; 6 | import org.apache.http.config.RegistryBuilder; 7 | import org.apache.http.conn.socket.ConnectionSocketFactory; 8 | import org.apache.http.conn.socket.PlainConnectionSocketFactory; 9 | import org.apache.http.conn.ssl.SSLConnectionSocketFactory; 10 | import org.apache.http.ssl.SSLContexts; 11 | import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; 12 | 13 | import javax.net.ssl.SSLContext; 14 | import java.security.KeyManagementException; 15 | import java.security.NoSuchAlgorithmException; 16 | import java.util.HashMap; 17 | import java.util.Map; 18 | import java.util.concurrent.TimeUnit; 19 | import java.util.concurrent.locks.Lock; 20 | import java.util.concurrent.locks.ReentrantLock; 21 | 22 | /** 23 | * Copyright [2016] Capital One Services, LLC 24 | * 25 | * Licensed under the Apache License, Version 2.0 (the "License"); 26 | * you may not use this file except in compliance with the License. 27 | * You may obtain a copy of the License at 28 | * 29 | * http://www.apache.org/licenses/LICENSE-2.0 30 | * 31 | * Unless required by applicable law or agreed to in writing, software 32 | * distributed under the License is distributed on an "AS IS" BASIS, 33 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 34 | * See the License for the specific language governing permissions and limitations under the License. 35 | */ 36 | public class HttpConnectionFactoryImpl implements HttpConnectionFactory { 37 | 38 | private final Map connectionPools = new HashMap<>(); 39 | private final Lock lock; 40 | 41 | public HttpConnectionFactoryImpl(Lock lock) { 42 | this.lock = lock; 43 | } 44 | 45 | public HttpConnectionFactoryImpl() { 46 | this(new ReentrantLock()); 47 | } 48 | 49 | /** 50 | * Note this can return null if it is not able to obtain a lock for your connection pool 51 | * 52 | * @param connectionConfig the configuration 53 | * @return the connection pool 54 | */ 55 | public HttpConnectionPool getConnectionPool(HttpConnectionConfig connectionConfig) { 56 | HttpConnectionPool pool = null; 57 | try { 58 | // use a re-entrant lock here as we can't easily track locks across the platform 59 | // a re-entrant lock will attempt to acquire a lock, unless it already holds it. 60 | // this should prepare the pool for reuse 61 | if (lock.tryLock(60, TimeUnit.SECONDS)) { 62 | if (connectionPools.containsKey(connectionConfig)) { 63 | return connectionPools.get(connectionConfig); 64 | } 65 | pool = newConnectionPool(connectionConfig); 66 | 67 | connectionPools.put(connectionConfig, pool); 68 | } 69 | } catch (InterruptedException e) { 70 | throw new LockInterruptedException("Thread interrupted while attempting to acquire lock", e); 71 | } finally { 72 | lock.unlock(); 73 | } 74 | return pool; 75 | } 76 | 77 | private HttpConnectionPool newConnectionPool(HttpConnectionConfig connectionConfig) { 78 | SSLContext sslContext; 79 | 80 | try { 81 | sslContext = SSLContexts.custom() 82 | .useProtocol(connectionConfig.getSslProtocol()) 83 | .build(); 84 | } catch (NoSuchAlgorithmException | KeyManagementException e) { 85 | throw new SSLContextException(String.format("No such SSL protocol: %s", connectionConfig.getSslProtocol()), e); 86 | } 87 | Registry socketFactoryRegistry = RegistryBuilder.create() 88 | .register("http", PlainConnectionSocketFactory.getSocketFactory()) 89 | .register("https", new SSLConnectionSocketFactory(sslContext)) 90 | .build(); 91 | 92 | PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry); 93 | connectionManager.setMaxTotal(connectionConfig.getMaxHttpConnections()); 94 | 95 | return new HttpConnectionPool(connectionManager, connectionConfig); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/main/java/com/capitalone/auth/oauth/factory/HttpConnectionPool.java: -------------------------------------------------------------------------------- 1 | package com.capitalone.auth.oauth.factory; 2 | 3 | import org.apache.http.client.HttpClient; 4 | import org.apache.http.client.config.RequestConfig; 5 | import org.apache.http.conn.HttpClientConnectionManager; 6 | import org.apache.http.impl.client.HttpClients; 7 | 8 | /** 9 | * Copyright [2016] Capital One Services, LLC 10 | * 11 | * Licensed under the Apache License, Version 2.0 (the "License"); 12 | * you may not use this file except in compliance with the License. 13 | * You may obtain a copy of the License at 14 | * 15 | * http://www.apache.org/licenses/LICENSE-2.0 16 | * 17 | * Unless required by applicable law or agreed to in writing, software 18 | * distributed under the License is distributed on an "AS IS" BASIS, 19 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 20 | * See the License for the specific language governing permissions and limitations under the License. 21 | */ 22 | public class HttpConnectionPool { 23 | 24 | private HttpClientConnectionManager connectionManager; 25 | private RequestConfig requestConfig; 26 | 27 | 28 | public HttpConnectionPool(HttpClientConnectionManager manager, HttpConnectionConfig config) { 29 | this.connectionManager = manager; 30 | this.requestConfig = RequestConfig.custom() 31 | .setConnectTimeout(config.getHttpConnectionTimeout()) 32 | .setSocketTimeout(config.getHttpSocketTimeout()) 33 | .build(); 34 | } 35 | 36 | public HttpClient getHttpClient() { 37 | return HttpClients.custom() 38 | .setConnectionManager(connectionManager) 39 | .setDefaultRequestConfig(requestConfig) 40 | .build(); 41 | } 42 | 43 | @Override 44 | public boolean equals(Object o) { 45 | if (this == o) { 46 | return true; 47 | } 48 | if (!(o instanceof HttpConnectionPool)) { 49 | return false; 50 | } 51 | 52 | HttpConnectionPool that = (HttpConnectionPool) o; 53 | 54 | if (connectionManager != null ? !connectionManager.equals(that.connectionManager) : that.connectionManager != null) { 55 | return false; 56 | } 57 | if (requestConfig != null ? !requestConfig.equals(that.requestConfig) : that.requestConfig != null) { 58 | return false; 59 | } 60 | return true; 61 | } 62 | 63 | @Override 64 | public int hashCode() { 65 | int result = connectionManager != null ? connectionManager.hashCode() : 0; 66 | result = 31 * result + (requestConfig != null ? requestConfig.hashCode() : 0); 67 | return result; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/com/capitalone/auth/oauth/framework/ClientCredentialsNotFoundException.java: -------------------------------------------------------------------------------- 1 | package com.capitalone.auth.oauth.framework; 2 | 3 | import java.net.URISyntaxException; 4 | 5 | /** 6 | * Copyright [2016] Capital One Services, LLC 7 | * 8 | * Licensed under the Apache License, Version 2.0 (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and limitations under the License. 18 | */ 19 | public class ClientCredentialsNotFoundException extends Exception { 20 | 21 | public ClientCredentialsNotFoundException(String msg) { 22 | super(msg); 23 | } 24 | 25 | public ClientCredentialsNotFoundException(String msg, URISyntaxException e) { 26 | super(msg, e); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/capitalone/auth/oauth/framework/OAuthClientCredentials.java: -------------------------------------------------------------------------------- 1 | package com.capitalone.auth.oauth.framework; 2 | 3 | import java.net.URI; 4 | 5 | /** 6 | * Copyright [2016] Capital One Services, LLC 7 | * 8 | * Licensed under the Apache License, Version 2.0 (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and limitations under the License. 18 | */ 19 | public class OAuthClientCredentials implements Cloneable { 20 | 21 | private String grantType; 22 | private String clientId; 23 | private String clientSecret; 24 | private URI authServerURI; 25 | private String clientURIRegex; 26 | private String clientSecretEncryptionKey; 27 | 28 | private OAuthClientCredentials(Builder builder) { 29 | grantType = builder.grantType; 30 | clientId = builder.clientId; 31 | clientSecret = builder.clientSecret; 32 | authServerURI = builder.authServerURI; 33 | clientURIRegex = builder.clientURIRegex; 34 | clientSecretEncryptionKey = builder.clientSecretEncryptionKey; 35 | } 36 | 37 | public static Builder newBuilder() { 38 | return new Builder(); 39 | } 40 | 41 | public String getGrantType() { 42 | return grantType; 43 | } 44 | 45 | public String getClientId() { 46 | return clientId; 47 | } 48 | 49 | public String getClientSecret() { 50 | return clientSecret; 51 | } 52 | 53 | public URI getAuthServerURI() { 54 | return authServerURI; 55 | } 56 | 57 | @Override 58 | public boolean equals(Object o) { 59 | if (this == o) { 60 | return true; 61 | } 62 | 63 | if (o == null || getClass() != o.getClass()) { 64 | return false; 65 | } 66 | 67 | OAuthClientCredentials that = (OAuthClientCredentials) o; 68 | 69 | if (grantType != null ? !grantType.equals(that.grantType) : that.grantType != null) { 70 | return false; 71 | } 72 | 73 | if (clientId != null ? !clientId.equals(that.clientId) : that.clientId != null) { 74 | return false; 75 | } 76 | 77 | if (clientSecret != null ? !clientSecret.equals(that.clientSecret) : that.clientSecret != null) { 78 | return false; 79 | } 80 | 81 | return authServerURI != null ? authServerURI.equals(that.authServerURI) : that.authServerURI == null; 82 | 83 | } 84 | 85 | @Override 86 | public int hashCode() { 87 | int result = grantType != null ? grantType.hashCode() : 0; 88 | result = 31 * result + (clientId != null ? clientId.hashCode() : 0); 89 | result = 31 * result + (clientSecret != null ? clientSecret.hashCode() : 0); 90 | result = 31 * result + (authServerURI != null ? authServerURI.hashCode() : 0); 91 | return result; 92 | } 93 | 94 | @Override 95 | public OAuthClientCredentials clone() { 96 | return OAuthClientCredentials.newBuilder() 97 | .grantType(grantType) 98 | .clientURIRegex(clientURIRegex) 99 | .clientId(clientId) 100 | .clientSecret(clientSecret) 101 | .authServerURI(authServerURI) 102 | .clientSecretEncryptionKey(clientSecretEncryptionKey) 103 | .build(); 104 | } 105 | 106 | public String getClientURIRegex() { 107 | return clientURIRegex; 108 | } 109 | 110 | public String getClientSecretEncryptionKey() { 111 | return clientSecretEncryptionKey; 112 | } 113 | 114 | public static final class Builder { 115 | private String grantType; 116 | private String clientId; 117 | private String clientSecret; 118 | private URI authServerURI; 119 | private String clientURIRegex; 120 | private String clientSecretEncryptionKey; 121 | 122 | private Builder() { 123 | } 124 | 125 | public Builder grantType(String val) { 126 | grantType = val; 127 | return this; 128 | } 129 | 130 | public Builder clientId(String val) { 131 | clientId = val; 132 | return this; 133 | } 134 | 135 | public Builder clientSecret(String val) { 136 | clientSecret = val; 137 | return this; 138 | } 139 | 140 | public Builder authServerURI(URI val) { 141 | authServerURI = val; 142 | return this; 143 | } 144 | 145 | public Builder clientURIRegex(String val) { 146 | clientURIRegex = val; 147 | return this; 148 | } 149 | 150 | public Builder clientSecretEncryptionKey(String val) { 151 | clientSecretEncryptionKey = val; 152 | return this; 153 | } 154 | 155 | 156 | public OAuthClientCredentials build() { 157 | return new OAuthClientCredentials(this); 158 | } 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /src/main/java/com/capitalone/auth/oauth/framework/OAuthClientCredentialsProvider.java: -------------------------------------------------------------------------------- 1 | package com.capitalone.auth.oauth.framework; 2 | 3 | import com.capitalone.auth.ClientCredentialsProvider; 4 | 5 | import java.net.URI; 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | 9 | /** 10 | * Copyright [2016] Capital One Services, LLC 11 | * 12 | * Licensed under the Apache License, Version 2.0 (the "License"); 13 | * you may not use this file except in compliance with the License. 14 | * You may obtain a copy of the License at 15 | * 16 | * http://www.apache.org/licenses/LICENSE-2.0 17 | * 18 | * Unless required by applicable law or agreed to in writing, software 19 | * distributed under the License is distributed on an "AS IS" BASIS, 20 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 21 | * See the License for the specific language governing permissions and limitations under the License. 22 | */ 23 | public class OAuthClientCredentialsProvider implements ClientCredentialsProvider { 24 | 25 | private List clientCredentialsList = new ArrayList<>(); 26 | 27 | public OAuthClientCredentialsProvider(OAuthClientCredentials[] clientCredentialsList) { 28 | setClientCredentialsList(clientCredentialsList); 29 | } 30 | 31 | public OAuthClientCredentialsProvider(OAuthClientCredentials clientCredentials) { 32 | this.clientCredentialsList.add(clientCredentials.clone()); 33 | } 34 | 35 | @Override 36 | public OAuthClientCredentials getClientCredentialsFor(URI uri) throws ClientCredentialsNotFoundException { 37 | for (OAuthClientCredentials clientCredentials : clientCredentialsList) { 38 | if (uri.toString().matches(clientCredentials.getClientURIRegex())) { 39 | return clientCredentials; 40 | } 41 | } 42 | 43 | throw new ClientCredentialsNotFoundException("client credentials not found"); 44 | } 45 | 46 | private void setClientCredentialsList(final OAuthClientCredentials[] incomingClientCredentialsList) { 47 | for (OAuthClientCredentials incomingClientCredentials : incomingClientCredentialsList) { 48 | this.clientCredentialsList.add(incomingClientCredentials.clone()); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/com/capitalone/auth/oauth/framework/protocol/ServerOAuthToken.java: -------------------------------------------------------------------------------- 1 | package com.capitalone.auth.oauth.framework.protocol; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | 5 | /** 6 | * Copyright [2016] Capital One Services, LLC 7 | * 8 | * Licensed under the Apache License, Version 2.0 (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and limitations under the License. 18 | */ 19 | public class ServerOAuthToken { 20 | 21 | @JsonProperty("access_token") 22 | private String accessToken; 23 | 24 | @JsonProperty("token_type") 25 | private String tokenType; 26 | 27 | @JsonProperty("expires_in") 28 | private long expiresIn; 29 | 30 | public ServerOAuthToken() { 31 | // required for jackson 32 | } 33 | 34 | public String getAccessToken() { 35 | return accessToken; 36 | } 37 | 38 | public String getTokenType() { 39 | return tokenType; 40 | } 41 | 42 | public long getExpiresIn() { 43 | return expiresIn; 44 | } 45 | 46 | public String getValue() { 47 | return accessToken; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/com/capitalone/auth/oauth/service/ClientSecretException.java: -------------------------------------------------------------------------------- 1 | package com.capitalone.auth.oauth.service; 2 | 3 | /** 4 | * Copyright [2016] Capital One Services, LLC 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and limitations under the License. 16 | */ 17 | public class ClientSecretException extends Exception { 18 | public ClientSecretException(String message, Throwable cause) { 19 | super(message, cause); 20 | } 21 | 22 | public ClientSecretException(String reason) { 23 | super(reason); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/capitalone/auth/oauth/service/ClientSecretService.java: -------------------------------------------------------------------------------- 1 | package com.capitalone.auth.oauth.service; 2 | 3 | import com.capitalone.auth.oauth.framework.OAuthClientCredentials; 4 | 5 | /** 6 | * Copyright [2016] Capital One Services, LLC 7 | * 8 | * Licensed under the Apache License, Version 2.0 (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and limitations under the License. 18 | */ 19 | public interface ClientSecretService { 20 | String obtainClientSecret(OAuthClientCredentials clientCredentials) throws ClientSecretException; 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/capitalone/auth/oauth/service/OAuthToken.java: -------------------------------------------------------------------------------- 1 | package com.capitalone.auth.oauth.service; 2 | 3 | import com.capitalone.auth.Token; 4 | 5 | /** 6 | * Copyright [2016] Capital One Services, LLC 7 | * 8 | * Licensed under the Apache License, Version 2.0 (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and limitations under the License. 18 | */ 19 | public class OAuthToken implements Token { 20 | 21 | private String accessToken; 22 | private String tokenType; 23 | private long expiresIn; 24 | private long expiresOn; 25 | private long creationTime; 26 | 27 | public OAuthToken() { 28 | this.creationTime = System.currentTimeMillis(); 29 | } 30 | 31 | private OAuthToken(Builder builder) { 32 | this(); 33 | this.accessToken = builder.accessToken; 34 | this.tokenType = builder.tokenType; 35 | this.expiresIn = builder.expiresIn - 10; // TODO: Needs to be configurable at some point. 36 | this.expiresOn = this.creationTime + (this.expiresIn * 1000); 37 | } 38 | 39 | public static Builder newBuilder() { 40 | return new Builder(); 41 | } 42 | 43 | @Override 44 | public String getValue() { 45 | return accessToken; 46 | } 47 | 48 | public boolean hasExpired() { 49 | long currentTime = System.currentTimeMillis(); 50 | return expiresOn <= currentTime; 51 | } 52 | 53 | public long getExpiresIn() { 54 | return expiresIn; 55 | } 56 | 57 | public long getCreationTime() { 58 | return creationTime; 59 | } 60 | 61 | public long getExpiresOn() { 62 | return expiresOn; 63 | } 64 | 65 | public long getRemainingTime() { 66 | long currentTime = System.currentTimeMillis(); 67 | long expiresOn = this.expiresOn; 68 | return expiresOn - currentTime; 69 | } 70 | 71 | public String getTokenType() { 72 | return tokenType; 73 | } 74 | 75 | public static final class Builder { 76 | private String accessToken; 77 | private String tokenType; 78 | private long expiresIn; 79 | 80 | public Builder() { 81 | } 82 | 83 | public Builder accessToken(String val) { 84 | accessToken = val; 85 | return this; 86 | } 87 | 88 | public Builder tokenType(String val) { 89 | tokenType = val; 90 | return this; 91 | } 92 | 93 | public Builder expiresIn(long val) { 94 | expiresIn = val; 95 | return this; 96 | } 97 | 98 | public OAuthToken build() { 99 | return new OAuthToken(this); 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/main/java/com/capitalone/auth/oauth/service/OAuthTokenAttributes.java: -------------------------------------------------------------------------------- 1 | package com.capitalone.auth.oauth.service; 2 | 3 | import java.util.concurrent.Future; 4 | import java.util.concurrent.locks.Lock; 5 | 6 | /** 7 | * Copyright [2016] Capital One Services, LLC 8 | * 9 | * Licensed under the Apache License, Version 2.0 (the "License"); 10 | * you may not use this file except in compliance with the License. 11 | * You may obtain a copy of the License at 12 | * 13 | * http://www.apache.org/licenses/LICENSE-2.0 14 | * 15 | * Unless required by applicable law or agreed to in writing, software 16 | * distributed under the License is distributed on an "AS IS" BASIS, 17 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18 | * See the License for the specific language governing permissions and limitations under the License. 19 | */ 20 | public class OAuthTokenAttributes { 21 | 22 | private OAuthToken token; 23 | private Lock lock; 24 | private Future job; 25 | 26 | private OAuthTokenAttributes(Builder builder) { 27 | setToken(builder.token); 28 | lock = builder.lock; 29 | job = builder.job; 30 | } 31 | 32 | public static Builder newBuilder() { 33 | return new Builder(); 34 | } 35 | 36 | public OAuthToken getToken() { 37 | return token; 38 | } 39 | 40 | public void setToken(OAuthToken token) { 41 | this.token = token; 42 | } 43 | 44 | public Lock getLock() { 45 | return lock; 46 | } 47 | 48 | public Future getJob() { 49 | return job; 50 | } 51 | 52 | public void setJob(Future job) { 53 | this.job = job; 54 | } 55 | 56 | public void clearJob() { 57 | job = null; 58 | } 59 | 60 | public static final class Builder { 61 | private OAuthToken token; 62 | private Lock lock; 63 | private Future job; 64 | 65 | private Builder() { 66 | } 67 | 68 | public OAuthTokenAttributes build() { 69 | return new OAuthTokenAttributes(this); 70 | } 71 | 72 | public Builder token(OAuthToken val) { 73 | token = val; 74 | return this; 75 | } 76 | 77 | public Builder lock(Lock val) { 78 | lock = val; 79 | return this; 80 | } 81 | 82 | public Builder job(Future val) { 83 | job = val; 84 | return this; 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/main/java/com/capitalone/auth/oauth/service/OAuthTokenService.java: -------------------------------------------------------------------------------- 1 | package com.capitalone.auth.oauth.service; 2 | 3 | import com.capitalone.auth.ClientCredentialsProvider; 4 | import com.capitalone.auth.Token; 5 | import com.capitalone.auth.TokenService; 6 | import com.capitalone.auth.oauth.factory.HttpConnectionConfig; 7 | import com.capitalone.auth.oauth.factory.HttpConnectionFactory; 8 | import com.capitalone.auth.oauth.factory.HttpConnectionPool; 9 | import com.capitalone.auth.oauth.framework.ClientCredentialsNotFoundException; 10 | import com.capitalone.auth.oauth.framework.OAuthClientCredentials; 11 | import com.capitalone.auth.oauth.framework.protocol.ServerOAuthToken; 12 | import com.fasterxml.jackson.databind.ObjectMapper; 13 | import org.apache.commons.io.IOUtils; 14 | import org.apache.http.HttpEntity; 15 | import org.apache.http.HttpResponse; 16 | import org.apache.http.NameValuePair; 17 | import org.apache.http.client.HttpClient; 18 | import org.apache.http.client.entity.UrlEncodedFormEntity; 19 | import org.apache.http.client.methods.HttpPost; 20 | import org.apache.http.message.BasicNameValuePair; 21 | 22 | import java.io.IOException; 23 | import java.net.URI; 24 | import java.util.ArrayList; 25 | import java.util.HashMap; 26 | import java.util.List; 27 | import java.util.Map; 28 | import java.util.concurrent.*; 29 | import java.util.concurrent.locks.Lock; 30 | import java.util.concurrent.locks.ReentrantLock; 31 | 32 | /** 33 | *

34 | * This manages oauth tokens. If a token is close to expiry, it will prefetch (on request). 35 | *

36 | * You just ask it for a token for the given uri (client uri) and it will work out which oauth server it will use 37 | * and manages locks etc for that service. 38 | * 39 | * Copyright [2016] Capital One Services, LLC 40 | * 41 | * Licensed under the Apache License, Version 2.0 (the "License"); 42 | * you may not use this file except in compliance with the License. 43 | * You may obtain a copy of the License at 44 | * 45 | * http://www.apache.org/licenses/LICENSE-2.0 46 | * 47 | * Unless required by applicable law or agreed to in writing, software 48 | * distributed under the License is distributed on an "AS IS" BASIS, 49 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 50 | * See the License for the specific language governing permissions and limitations under the License. 51 | */ 52 | public class OAuthTokenService implements TokenService { 53 | 54 | public static final String KEY_GRANT_TYPE = "grant_type"; 55 | public static final String KEY_CLIENT_ID = "client_id"; 56 | public static final String KEY_CLIENT_SECRET = "client_secret"; 57 | 58 | private ObjectMapper objectMapper = new ObjectMapper(); 59 | private ClientCredentialsProvider clientCredentialsProvider; 60 | private int prefetchTimeout; 61 | private ClientSecretService clientSecretService; 62 | private Lock lock = new ReentrantLock(); 63 | private HttpConnectionPool httpConnectionPool; 64 | private Map tokenCache = new HashMap<>(); 65 | private ExecutorService executorService; 66 | 67 | /** 68 | * Creates an oauth token service that is responsible for managing oauth tokens. 69 | * 70 | * @param httpConnectionFactory the http connection factory to use for getting oauth tokens 71 | * @param prefetchTimeout the prefetch buffer (in milliseconds) 72 | */ 73 | public OAuthTokenService(HttpConnectionFactory httpConnectionFactory, HttpConnectionConfig httpConnectionConfig, int prefetchPoolSize, 74 | int prefetchTimeout, ClientCredentialsProvider oAuthClientCredentialsProvider, ClientSecretService clientSecretService) { 75 | this.prefetchTimeout = prefetchTimeout; 76 | this.clientSecretService = clientSecretService; 77 | this.httpConnectionPool = httpConnectionFactory.getConnectionPool(httpConnectionConfig); 78 | this.executorService = Executors.newFixedThreadPool(prefetchPoolSize); 79 | this.clientCredentialsProvider = oAuthClientCredentialsProvider; 80 | } 81 | 82 | @Override 83 | public Token obtainTokenFor(URI uri) throws IOException { 84 | final OAuthClientCredentials clientCredentials; 85 | try { 86 | clientCredentials = clientCredentialsProvider.getClientCredentialsFor(uri); 87 | } catch (ClientCredentialsNotFoundException e) { 88 | throw new IOException("oauth configuration not found for uri", e); 89 | } 90 | 91 | OAuthTokenAttributes oauthTokenAttributes = null; 92 | 93 | // global lock here - manage the individual locks. must be locked globally to avoid creating 94 | // multiple lock objects for the same url. 95 | try { 96 | if (lock.tryLock(10, TimeUnit.SECONDS)) { 97 | if (tokenCache.containsKey(clientCredentials)) { 98 | oauthTokenAttributes = tokenCache.get(clientCredentials); 99 | } else { 100 | oauthTokenAttributes = OAuthTokenAttributes.newBuilder() 101 | .token(null) 102 | .lock(new ReentrantLock()) 103 | .build(); 104 | 105 | tokenCache.put(clientCredentials, oauthTokenAttributes); 106 | } 107 | } else { 108 | throw new IOException("failed to acquire global lock in time"); 109 | } 110 | } catch (InterruptedException e) { 111 | throw new IOException("error acquiring global lock", e); 112 | } finally { 113 | lock.unlock(); 114 | } 115 | 116 | // now get the lock for the individual oauth server 117 | Lock oauthTokenAttributesLock = oauthTokenAttributes.getLock(); 118 | try { 119 | // lock it with a timeout 120 | if (oauthTokenAttributesLock.tryLock(10, TimeUnit.SECONDS)) { 121 | 122 | // check to see if the token has expired 123 | OAuthToken token = oauthTokenAttributes.getToken(); 124 | if (token == null || token.hasExpired()) { 125 | 126 | // it has expired, so check if we have a prefetch job in progress, if so wait for it 127 | final Future inFlightJob = oauthTokenAttributes.getJob(); 128 | 129 | if (null != inFlightJob) { 130 | token = inFlightJob.get(); 131 | oauthTokenAttributes.clearJob(); 132 | } 133 | 134 | // token could have been replaced by prefetch job, so check to make sure that it has not expired 135 | if (null == token || token.hasExpired()) { 136 | final OAuthTokenRequestTask oauthTokenRequestTask = new OAuthTokenRequestTask(clientCredentials, httpConnectionPool, objectMapper, clientSecretService); 137 | token = oauthTokenRequestTask.call(); 138 | } 139 | } 140 | 141 | // now set the valid token 142 | oauthTokenAttributes.setToken(token); 143 | 144 | // and if we are close to expiry, start a job to get it 145 | if (this.prefetchTimeout > token.getRemainingTime() && null == oauthTokenAttributes.getJob()) { 146 | final OAuthTokenRequestTask oauthTokenRequestTask = new OAuthTokenRequestTask(clientCredentials, httpConnectionPool, objectMapper, clientSecretService); 147 | final Future job = executorService.submit(oauthTokenRequestTask); 148 | oauthTokenAttributes.setJob(job); 149 | } 150 | 151 | return token; 152 | } else { 153 | throw new IOException("failed to acquire lock in time for " + clientCredentials.getAuthServerURI()); 154 | } 155 | } catch (IOException e) { 156 | throw new IOException("Could not get authorisation from server", e); 157 | } catch (InterruptedException e) { 158 | throw new IOException("error acquiring lock for " + clientCredentials.getAuthServerURI(), e); 159 | } catch (ExecutionException e) { 160 | throw new IOException("error requesting oauth token", e); 161 | } catch (ClientSecretException e) { 162 | throw new IOException("error obtaining client secret", e); 163 | } finally { 164 | oauthTokenAttributesLock.unlock(); 165 | } 166 | } 167 | 168 | ObjectMapper getObjectMapper() { 169 | return objectMapper; 170 | } 171 | 172 | void setObjectMapper(ObjectMapper objectMapper) { 173 | this.objectMapper = objectMapper; 174 | } 175 | 176 | ClientCredentialsProvider getClientCredentialsProvider() { 177 | return clientCredentialsProvider; 178 | } 179 | 180 | HttpConnectionPool getHttpConnectionPool() { 181 | return httpConnectionPool; 182 | } 183 | 184 | void setHttpConnectionPool(HttpConnectionPool httpConnectionPool) { 185 | this.httpConnectionPool = httpConnectionPool; 186 | } 187 | 188 | void putToken(OAuthClientCredentials clientCredentials, OAuthTokenAttributes oauthTokenAttributes) { 189 | this.tokenCache.put(clientCredentials, oauthTokenAttributes); 190 | } 191 | 192 | Map getTokenCache() { 193 | return tokenCache; 194 | } 195 | 196 | void setLock(Lock lock) { 197 | this.lock = lock; 198 | } 199 | 200 | ExecutorService getExecutorService() { 201 | return executorService; 202 | } 203 | 204 | void setExecutorService(ExecutorService executorService) { 205 | this.executorService = executorService; 206 | } 207 | 208 | private static final class OAuthTokenRequestTask implements Callable { 209 | private OAuthClientCredentials clientCredentials; 210 | private HttpConnectionPool httpConnectionPool; 211 | private ObjectMapper objectMapper; 212 | private ClientSecretService clientSecretService; 213 | 214 | public OAuthTokenRequestTask(OAuthClientCredentials clientCredentials, HttpConnectionPool httpConnectionPool, ObjectMapper objectMapper, ClientSecretService clientSecretService) { 215 | this.clientCredentials = clientCredentials; 216 | this.httpConnectionPool = httpConnectionPool; 217 | this.objectMapper = objectMapper; 218 | this.clientSecretService = clientSecretService; 219 | } 220 | 221 | @Override 222 | public OAuthToken call() throws IOException, ClientSecretException { 223 | final HttpClient httpClient = this.httpConnectionPool.getHttpClient(); 224 | 225 | final List urlParameters = new ArrayList<>(); 226 | urlParameters.add(new BasicNameValuePair(KEY_CLIENT_ID, clientCredentials.getClientId())); 227 | 228 | String clientSecret = clientSecretService.obtainClientSecret(clientCredentials); 229 | 230 | urlParameters.add(new BasicNameValuePair(KEY_CLIENT_SECRET, clientSecret)); 231 | urlParameters.add(new BasicNameValuePair(KEY_GRANT_TYPE, clientCredentials.getGrantType())); 232 | 233 | final HttpPost httpPost = new HttpPost(clientCredentials.getAuthServerURI()); 234 | httpPost.setEntity(new UrlEncodedFormEntity(urlParameters)); 235 | 236 | final HttpResponse httpResponse = httpClient.execute(httpPost); 237 | final HttpEntity entity = httpResponse.getEntity(); 238 | final String content = IOUtils.toString(entity.getContent()); 239 | final ServerOAuthToken serverToken = this.objectMapper.readValue(content, ServerOAuthToken.class); 240 | final OAuthToken token = OAuthToken.newBuilder() 241 | .accessToken(serverToken.getAccessToken()) 242 | .tokenType(serverToken.getTokenType()) 243 | .expiresIn(serverToken.getExpiresIn()) 244 | .build(); 245 | 246 | return token; 247 | } 248 | } 249 | } 250 | -------------------------------------------------------------------------------- /src/test/java/com/capitalone/auth/oauth/factory/HttpConnectionFactoryImplTest.java: -------------------------------------------------------------------------------- 1 | package com.capitalone.auth.oauth.factory; 2 | 3 | import com.capitalone.auth.oauth.exceptions.LockInterruptedException; 4 | import com.capitalone.auth.oauth.exceptions.SSLContextException; 5 | import junit.framework.TestCase; 6 | import org.junit.Test; 7 | 8 | import java.util.concurrent.TimeUnit; 9 | import java.util.concurrent.locks.Lock; 10 | 11 | import static org.hamcrest.CoreMatchers.*; 12 | import static org.hamcrest.MatcherAssert.assertThat; 13 | import static org.hamcrest.core.Is.is; 14 | import static org.mockito.Matchers.any; 15 | import static org.mockito.Matchers.anyLong; 16 | import static org.mockito.Matchers.eq; 17 | import static org.mockito.Mockito.*; 18 | 19 | /** 20 | * Copyright [2016] Capital One Services, LLC 21 | * 22 | * Licensed under the Apache License, Version 2.0 (the "License"); 23 | * you may not use this file except in compliance with the License. 24 | * You may obtain a copy of the License at 25 | * 26 | * http://www.apache.org/licenses/LICENSE-2.0 27 | * 28 | * Unless required by applicable law or agreed to in writing, software 29 | * distributed under the License is distributed on an "AS IS" BASIS, 30 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 31 | * See the License for the specific language governing permissions and limitations under the License. 32 | */ 33 | public class HttpConnectionFactoryImplTest { 34 | 35 | @Test 36 | public void testGetConnectionClient() throws Exception { 37 | HttpConnectionFactoryImpl testee = new HttpConnectionFactoryImpl(); 38 | 39 | final HttpConnectionPool connectionPool = testee.getConnectionPool(HttpConnectionConfig.newBuilder().httpConnectionTimeout(60).httpSocketTimeout(60).maxHttpConnections(20).build()); 40 | assertThat(connectionPool, is(notNullValue())); 41 | 42 | // now again should return the same instance 43 | final HttpConnectionPool connectionPool2 = testee.getConnectionPool(HttpConnectionConfig.newBuilder().httpConnectionTimeout(60).httpSocketTimeout(60).maxHttpConnections(20).build()); 44 | assertThat(connectionPool2, is(sameInstance(connectionPool))); 45 | 46 | // it should give a new connection pool if the config params are different 47 | final HttpConnectionPool connectionPool3 = testee.getConnectionPool(HttpConnectionConfig.newBuilder().httpConnectionTimeout(120).httpSocketTimeout(60).maxHttpConnections(20).build()); 48 | assertThat(connectionPool3, is(not(sameInstance(connectionPool)))); 49 | 50 | // test with specific SSL protocol version 51 | final HttpConnectionPool connectionPool4 = testee.getConnectionPool( 52 | HttpConnectionConfig.newBuilder().httpConnectionTimeout(120).httpSocketTimeout(60).maxHttpConnections(20).sslProtocol("TLSv1.2").build()); 53 | assertThat(connectionPool4, is(notNullValue())); 54 | } 55 | 56 | @Test (expected = SSLContextException.class) 57 | public void shouldThrowSSLContextExceptionIfSSLProtocolIsInvalid() { 58 | HttpConnectionFactoryImpl httpConnectionFactoryImpl = new HttpConnectionFactoryImpl(); 59 | 60 | httpConnectionFactoryImpl.getConnectionPool( 61 | HttpConnectionConfig.newBuilder().httpConnectionTimeout(60).httpSocketTimeout(60).maxHttpConnections(20).sslProtocol("INVALID_PROTOCOL").build()); 62 | } 63 | 64 | @Test 65 | public void testUsesLockForThreadSafety() throws Exception { 66 | Lock mockLock = mock(Lock.class); 67 | HttpConnectionFactoryImpl testee = new HttpConnectionFactoryImpl(mockLock); 68 | 69 | testee.getConnectionPool(HttpConnectionConfig.newBuilder().httpConnectionTimeout(120).httpSocketTimeout(60).maxHttpConnections(20).build()); 70 | 71 | verify(mockLock).tryLock(eq(60L), eq(TimeUnit.SECONDS)); 72 | verify(mockLock).unlock(); 73 | } 74 | 75 | @Test 76 | public void testUnlockEvenIfItThrows() throws Exception { 77 | Lock mockLock = mock(Lock.class); 78 | HttpConnectionFactoryImpl testee = new HttpConnectionFactoryImpl(mockLock); 79 | 80 | when(mockLock.tryLock(anyLong(), any(TimeUnit.class))).thenThrow(new InterruptedException("something")); 81 | 82 | try { 83 | testee.getConnectionPool(HttpConnectionConfig.newBuilder().httpConnectionTimeout(120).httpSocketTimeout(60).maxHttpConnections(20).build()); 84 | TestCase.fail("should have thrown InterruptedException"); 85 | } catch (LockInterruptedException e) { 86 | verify(mockLock).unlock(); 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/test/java/com/capitalone/auth/oauth/framework/OAuthClientCredentialsProviderTest.java: -------------------------------------------------------------------------------- 1 | package com.capitalone.auth.oauth.framework; 2 | 3 | import junit.framework.TestCase; 4 | import org.junit.Before; 5 | import org.junit.Test; 6 | 7 | import java.net.URI; 8 | import java.net.URISyntaxException; 9 | 10 | import static org.hamcrest.MatcherAssert.assertThat; 11 | import static org.hamcrest.Matchers.*; 12 | 13 | /** 14 | * Copyright [2016] Capital One Services, LLC 15 | * 16 | * Licensed under the Apache License, Version 2.0 (the "License"); 17 | * you may not use this file except in compliance with the License. 18 | * You may obtain a copy of the License at 19 | * 20 | * http://www.apache.org/licenses/LICENSE-2.0 21 | * 22 | * Unless required by applicable law or agreed to in writing, software 23 | * distributed under the License is distributed on an "AS IS" BASIS, 24 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 25 | * See the License for the specific language governing permissions and limitations under the License. 26 | */ 27 | public class OAuthClientCredentialsProviderTest { 28 | 29 | private OAuthClientCredentialsProvider testee; 30 | private OAuthClientCredentials fakeClientCredentials; 31 | 32 | @Before 33 | public void setup() throws URISyntaxException { 34 | fakeClientCredentials = OAuthClientCredentials.newBuilder() 35 | .clientId("clientId") 36 | .clientSecret("clientSecret") 37 | .authServerURI(new URI("https://my.oauth.club/")) 38 | .grantType("grantType") 39 | .clientURIRegex("^http[s]{0,1}://my.service.to.be.authorised.com[/]{0,1}$") 40 | .clientSecretEncryptionKey("clientSecretEncryptionKey") 41 | .build(); 42 | 43 | OAuthClientCredentialsProvider oAuthClientCredentialsProvider = new OAuthClientCredentialsProvider(new OAuthClientCredentials[]{fakeClientCredentials}); 44 | testee = oAuthClientCredentialsProvider; 45 | } 46 | 47 | @Test 48 | public void testGetClientCredentialsFor() throws Exception { 49 | final OAuthClientCredentials clientCredentials = testee.getClientCredentialsFor(new URI("https://my.service.to.be.authorised.com")); 50 | assertThat(clientCredentials.getGrantType(), is(equalTo("grantType"))); 51 | assertThat(clientCredentials.getClientId(), is(equalTo("clientId"))); 52 | assertThat(clientCredentials.getClientSecret(), is(equalTo("clientSecret"))); 53 | assertThat(clientCredentials.getClientSecretEncryptionKey(), is(equalTo("clientSecretEncryptionKey"))); 54 | assertThat(clientCredentials.getAuthServerURI(), is(equalTo(new URI("https://my.oauth.club/")))); 55 | } 56 | 57 | @Test 58 | public void testGetClientCredentialsFor_clientURIDoesNotMatch() throws Exception { 59 | try { 60 | testee.getClientCredentialsFor(new URI("https://not.my.service.to.be.authorised.com")); 61 | TestCase.fail("exception expected"); 62 | } catch (ClientCredentialsNotFoundException e) { 63 | assertThat(e.getMessage(), is(equalTo("client credentials not found"))); 64 | } 65 | } 66 | 67 | @Test 68 | public void testWhenComparedWithDifferentInstanceWithSameValuesThenItReturnsTrue() throws Exception { 69 | final URI uri = new URI("https://my.service.to.be.authorised.com"); 70 | final OAuthClientCredentials clientCredentials1 = testee.getClientCredentialsFor(uri); 71 | final OAuthClientCredentials clientCredentials2 = testee.getClientCredentialsFor(uri); 72 | 73 | assertThat(clientCredentials1, is(sameInstance(clientCredentials2))); 74 | assertThat(clientCredentials1, is(equalTo(clientCredentials2))); 75 | assertThat(clientCredentials2, is(equalTo(clientCredentials1))); 76 | } 77 | } -------------------------------------------------------------------------------- /src/test/java/com/capitalone/auth/oauth/service/OAuthTokenServiceTest.java: -------------------------------------------------------------------------------- 1 | package com.capitalone.auth.oauth.service; 2 | 3 | import com.capitalone.auth.ClientCredentialsProvider; 4 | import com.capitalone.auth.Token; 5 | import com.capitalone.auth.oauth.factory.HttpConnectionConfig; 6 | import com.capitalone.auth.oauth.factory.HttpConnectionFactory; 7 | import com.capitalone.auth.oauth.factory.HttpConnectionPool; 8 | import com.capitalone.auth.oauth.framework.ClientCredentialsNotFoundException; 9 | import com.capitalone.auth.oauth.framework.OAuthClientCredentials; 10 | import com.fasterxml.jackson.databind.ObjectMapper; 11 | import junit.framework.TestCase; 12 | import org.apache.http.HttpResponse; 13 | import org.apache.http.StatusLine; 14 | import org.apache.http.client.HttpClient; 15 | import org.apache.http.client.entity.UrlEncodedFormEntity; 16 | import org.apache.http.client.methods.HttpPost; 17 | import org.apache.http.entity.StringEntity; 18 | import org.apache.http.util.EntityUtils; 19 | import org.junit.Before; 20 | import org.junit.Test; 21 | import org.mockito.ArgumentCaptor; 22 | import org.mockito.Mockito; 23 | 24 | import java.io.IOException; 25 | import java.net.URI; 26 | import java.util.concurrent.*; 27 | import java.util.concurrent.locks.Lock; 28 | 29 | import static org.hamcrest.CoreMatchers.*; 30 | import static org.hamcrest.Matchers.equalTo; 31 | import static org.hamcrest.Matchers.instanceOf; 32 | import static org.hamcrest.core.Is.is; 33 | import static org.junit.Assert.assertThat; 34 | import static org.junit.Assert.fail; 35 | import static org.mockito.Matchers.any; 36 | import static org.mockito.Matchers.anyLong; 37 | import static org.mockito.Matchers.eq; 38 | import static org.mockito.Mockito.*; 39 | 40 | /** 41 | * Copyright [2016] Capital One Services, LLC 42 | * 43 | * Licensed under the Apache License, Version 2.0 (the "License"); 44 | * you may not use this file except in compliance with the License. 45 | * You may obtain a copy of the License at 46 | * 47 | * http://www.apache.org/licenses/LICENSE-2.0 48 | * 49 | * Unless required by applicable law or agreed to in writing, software 50 | * distributed under the License is distributed on an "AS IS" BASIS, 51 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 52 | * See the License for the specific language governing permissions and limitations under the License. 53 | */ 54 | public class OAuthTokenServiceTest { 55 | 56 | private OAuthTokenService testee; 57 | private HttpConnectionFactory mockFactory; 58 | private HttpConnectionPool mockPool; 59 | private ClientCredentialsProvider mockProvider; 60 | private Lock mockLock; 61 | private ClientSecretService mockClientSecretService; 62 | 63 | @Before 64 | public void setup() { 65 | mockPool = mock(HttpConnectionPool.class); 66 | mockFactory = mock(HttpConnectionFactory.class); 67 | when(mockFactory.getConnectionPool(eq(HttpConnectionConfig.newBuilder().httpConnectionTimeout(60).httpSocketTimeout(40).maxHttpConnections(20).build()))).thenReturn(mockPool); 68 | 69 | mockProvider = mock(ClientCredentialsProvider.class); 70 | 71 | HttpConnectionConfig httpConnectionConfig = HttpConnectionConfig.newBuilder() 72 | .httpConnectionTimeout(60) 73 | .httpSocketTimeout(40) 74 | .maxHttpConnections(20) 75 | .build(); 76 | 77 | mockClientSecretService = mock(ClientSecretService.class); 78 | 79 | testee = new OAuthTokenService(mockFactory, httpConnectionConfig, 20, 20000, mockProvider, mockClientSecretService); 80 | 81 | testee.setHttpConnectionPool(mockPool); 82 | 83 | mockLock = mock(Lock.class); 84 | } 85 | 86 | @Test 87 | public void testConstructedWithAFixedSizeExecutorPool() throws Exception { 88 | 89 | HttpConnectionFactory mockConnectionFactory = mock(HttpConnectionFactory.class); 90 | 91 | HttpConnectionConfig httpConnectionConfig = HttpConnectionConfig.newBuilder() 92 | .httpConnectionTimeout(60) 93 | .httpSocketTimeout(40) 94 | .maxHttpConnections(20) 95 | .build(); 96 | 97 | OAuthTokenService newInstance = new OAuthTokenService(mockConnectionFactory, httpConnectionConfig, 10, 10, mockProvider, mockClientSecretService); 98 | 99 | assertThat(newInstance.getExecutorService(), instanceOf(ThreadPoolExecutor.class)); 100 | } 101 | 102 | @Test 103 | public void testObtainTokenFor_requestNewToken() throws Exception { 104 | testee.setObjectMapper(new ObjectMapper()); 105 | 106 | final HttpClient mockClient = mock(HttpClient.class); 107 | when(mockPool.getHttpClient()).thenReturn(mockClient); 108 | 109 | final HttpResponse mockHttpResponse = mock(HttpResponse.class); 110 | when(mockClient.execute(any(HttpPost.class))).thenReturn(mockHttpResponse); 111 | 112 | final StatusLine mockStatusLine = mock(StatusLine.class); 113 | when(mockHttpResponse.getStatusLine()).thenReturn(mockStatusLine); 114 | when(mockStatusLine.getStatusCode()).thenReturn(200); 115 | when(mockHttpResponse.getEntity()).thenReturn(new StringEntity("{\n" + 116 | " \"access_token\": \"sparkpost-token\",\n" + 117 | " \"token_type\": \"Bearer\",\n" + 118 | " \"expires_in\": 60\n" + 119 | "}")); 120 | 121 | final OAuthClientCredentials clientCredentials = OAuthClientCredentials.newBuilder().clientId("xyz").clientSecret("abc").grantType("client_credentials").authServerURI(new URI("https://my.oauth.club/")).build(); 122 | when(mockProvider.getClientCredentialsFor(any(URI.class))).thenReturn(clientCredentials); 123 | 124 | when(mockClientSecretService.obtainClientSecret(clientCredentials)).thenReturn("abc"); 125 | 126 | final URI uri = new URI("https://my.service.to.be.authorised.com/"); 127 | 128 | final Token token = testee.obtainTokenFor(uri); 129 | assertThat(token.getValue(), is("sparkpost-token")); 130 | 131 | final ArgumentCaptor captor = ArgumentCaptor.forClass(HttpPost.class); 132 | verify(mockClient).execute(captor.capture()); 133 | assertThat(captor.getValue().getURI(), is(equalTo(new URI("https://my.oauth.club/")))); 134 | assertThat(captor.getValue().getEntity(), is(notNullValue())); 135 | assertThat(captor.getValue().getEntity(), is(instanceOf(UrlEncodedFormEntity.class))); 136 | 137 | final String entityContent = EntityUtils.toString(captor.getValue().getEntity()); 138 | assertThat(entityContent, containsString("grant_type=client_credentials")); 139 | assertThat(entityContent, containsString("client_id=xyz")); 140 | assertThat(entityContent, containsString("client_secret=abc")); 141 | 142 | verify(mockProvider).getClientCredentialsFor(eq(new URI("https://my.service.to.be.authorised.com/"))); 143 | OAuthTokenAttributes cacheAttributes = testee.getTokenCache().get(clientCredentials); 144 | assertThat(cacheAttributes.getToken(), sameInstance(token)); 145 | } 146 | 147 | @Test(expected = IOException.class) 148 | public void testIOExceptionBubbles() throws Exception { 149 | when(mockProvider.getClientCredentialsFor(Mockito.any(URI.class))).thenReturn(OAuthClientCredentials.newBuilder().clientId("id").clientSecret("Secret").grantType("grant").build()); 150 | 151 | final URI uri = new URI("https://my.service.to.be.authorised.com/"); 152 | 153 | final HttpClient mockClient = mock(HttpClient.class); 154 | when(mockPool.getHttpClient()).thenReturn(mockClient); 155 | when(mockClient.execute(any(HttpPost.class))).thenThrow(IOException.class); 156 | 157 | testee.obtainTokenFor(uri); 158 | } 159 | 160 | @Test 161 | public void testHttpConnectionPoolIsCreated() throws Exception { 162 | final ArgumentCaptor captor = ArgumentCaptor.forClass(HttpConnectionConfig.class); 163 | verify(mockFactory).getConnectionPool(captor.capture()); 164 | assertThat(captor.getValue(), is(equalTo(HttpConnectionConfig.newBuilder().httpConnectionTimeout(60).httpSocketTimeout(40).maxHttpConnections(20).build()))); 165 | } 166 | 167 | @Test 168 | public void testIsDependencyInjected() throws Exception { 169 | assertThat(testee.getHttpConnectionPool(), is(notNullValue())); 170 | assertThat(testee.getClientCredentialsProvider(), is(notNullValue())); 171 | assertThat(testee.getObjectMapper(), is(notNullValue())); 172 | } 173 | 174 | @Test 175 | public void testIOExceptionWhenClientCredentialsNotFoundException() throws Exception { 176 | final ClientCredentialsNotFoundException cause = new ClientCredentialsNotFoundException("client credentials not found"); 177 | when(mockProvider.getClientCredentialsFor(any(URI.class))).thenThrow(cause); 178 | 179 | try { 180 | testee.obtainTokenFor(new URI("https://my.service.to.be.authorised.com/")); 181 | TestCase.fail("exception expected"); 182 | } catch (IOException e) { 183 | assertThat(e.getMessage(), is(equalTo("oauth configuration not found for uri"))); 184 | assertThat(e.getCause(), is(sameInstance((Throwable) cause))); 185 | } 186 | } 187 | 188 | @Test 189 | public void testWhenValidTokenAlreadyExistsThenWeReturnTheSameToken() throws Exception { 190 | 191 | OAuthToken fakeCachedToken = OAuthToken.newBuilder().accessToken("whatever").tokenType("good").expiresIn(60).build(); 192 | 193 | URI fakeUri = new URI("https://my.service.to.be.authorised.com/"); 194 | 195 | OAuthClientCredentials fakeClientCredentials = OAuthClientCredentials.newBuilder().clientId("123").clientSecret("s3cret").authServerURI(new URI("http://my_auth_srv")).grantType("whateveh").build(); 196 | 197 | when(mockProvider.getClientCredentialsFor(eq(fakeUri))).thenReturn(fakeClientCredentials); 198 | 199 | when(mockLock.tryLock(anyLong(), any(TimeUnit.class))).thenReturn(true); 200 | OAuthTokenAttributes fakeOAuthTokenAttributes = OAuthTokenAttributes.newBuilder() 201 | .token(fakeCachedToken) 202 | .lock(mockLock) 203 | .build(); 204 | testee.putToken(fakeClientCredentials, fakeOAuthTokenAttributes); 205 | 206 | OAuthToken receivedToken = (OAuthToken) testee.obtainTokenFor(fakeUri); 207 | 208 | assertThat(receivedToken, is(sameInstance(fakeCachedToken))); 209 | 210 | verifyNoMoreInteractions(mockPool); 211 | } 212 | 213 | @Test 214 | public void testWhenURIsRequestingSameTokenAreFoundThenSameTokenIsCached() throws Exception { 215 | URI fakeUri1 = new URI("http://fakeserver.fakedomain.fake.com"); 216 | URI fakeUri2 = new URI("http://fakeserver.fakedomain.fake.com/"); 217 | 218 | final OAuthClientCredentials clientCredentials1 = OAuthClientCredentials.newBuilder().clientId("xyz").clientSecret("abc").grantType("client_credentials").authServerURI(new URI("https://my.oauth.club/")).build(); 219 | final OAuthClientCredentials clientCredentials2 = OAuthClientCredentials.newBuilder().clientId("xyz").clientSecret("abc").grantType("client_credentials").authServerURI(new URI("https://my.oauth.club/")).build(); 220 | when(mockProvider.getClientCredentialsFor(eq(fakeUri1))).thenReturn(clientCredentials1); 221 | when(mockProvider.getClientCredentialsFor(eq(fakeUri2))).thenReturn(clientCredentials2); 222 | 223 | when(mockLock.tryLock(anyLong(), any(TimeUnit.class))).thenReturn(true); 224 | 225 | OAuthToken fakeToken1 = OAuthToken.newBuilder().accessToken("dis_yo_access").tokenType("nice").expiresIn(120).build(); 226 | OAuthTokenAttributes fakeTokenAttributes = OAuthTokenAttributes.newBuilder() 227 | .token(fakeToken1) 228 | .lock(mockLock) 229 | .build(); 230 | 231 | testee.putToken(clientCredentials1, fakeTokenAttributes); 232 | 233 | Token receivedToken1 = testee.obtainTokenFor(fakeUri2); 234 | 235 | assertThat(testee.getTokenCache().size(), is(equalTo(1))); 236 | assertThat(receivedToken1, is(sameInstance((Token) fakeToken1))); 237 | 238 | verifyNoMoreInteractions(mockPool); 239 | } 240 | 241 | @Test 242 | public void testWhenTokenIsInMatureStateThenPrefetchIsInvoked() throws Exception { 243 | final ExecutorService mockExecutorService = mock(ExecutorService.class); 244 | 245 | testee.setExecutorService(mockExecutorService); 246 | 247 | final Future mockFuture = mock(Future.class); 248 | when(mockExecutorService.submit(any(Callable.class))).thenReturn(mockFuture); 249 | 250 | URI fakeUri = new URI("http://fakeserver.fakedomain.fake.com"); 251 | 252 | final OAuthClientCredentials clientCredentials = OAuthClientCredentials.newBuilder().clientId("xyz").clientSecret("abc").grantType("client_credentials").authServerURI(new URI("https://my.oauth.club/")).build(); 253 | 254 | when(mockProvider.getClientCredentialsFor(eq(fakeUri))).thenReturn(clientCredentials); 255 | 256 | OAuthToken mockToken = Mockito.mock(OAuthToken.class); 257 | when(mockToken.getRemainingTime()).thenReturn(5L); 258 | 259 | when(mockLock.tryLock(anyLong(), any(TimeUnit.class))).thenReturn(true); 260 | 261 | OAuthTokenAttributes fakeOAuthTokenAttributes = OAuthTokenAttributes.newBuilder() 262 | .token(mockToken) 263 | .lock(mockLock) 264 | .build(); 265 | testee.putToken(clientCredentials, fakeOAuthTokenAttributes); 266 | 267 | when(mockLock.tryLock(eq(10L), eq(TimeUnit.SECONDS))).thenReturn(true); 268 | 269 | // test 270 | testee.obtainTokenFor(fakeUri); 271 | 272 | verify(mockLock).tryLock(eq(10L), eq(TimeUnit.SECONDS)); 273 | verify(mockLock).unlock(); 274 | assertThat(testee.getTokenCache().get(clientCredentials).getJob(), is(notNullValue())); 275 | 276 | ArgumentCaptor captor = ArgumentCaptor.forClass(Callable.class); 277 | verify(mockExecutorService).submit(captor.capture()); 278 | 279 | reset(); 280 | 281 | // part 2... what happens when we calll this callable??? 282 | final Callable actualCallable = captor.getValue(); 283 | 284 | final HttpClient mockClient = mock(HttpClient.class); 285 | when(this.mockPool.getHttpClient()).thenReturn(mockClient); 286 | 287 | final HttpResponse mockResponse = mock(HttpResponse.class); 288 | when(mockClient.execute(any(HttpPost.class))).thenReturn(mockResponse); 289 | when(mockResponse.getEntity()).thenReturn(new StringEntity("{\n" + 290 | " \"access_token\": \"sparkpost-token\",\n" + 291 | " \"token_type\": \"Bearer\",\n" + 292 | " \"expires_in\": 60\n" + 293 | "}")); 294 | 295 | //test.. 296 | final OAuthToken token = actualCallable.call(); 297 | assertThat(token.getValue(), is(equalTo("sparkpost-token"))); 298 | assertThat(token.getTokenType(), is(equalTo("Bearer"))); 299 | 300 | // expire 10 seconds less than actual expiry 301 | assertThat(token.getExpiresIn(), is(equalTo(50L))); 302 | } 303 | 304 | @Test 305 | public void testWhenTokenIsNewThenPrefetchIsNotInvoked() throws Exception { 306 | URI fakeUri = new URI("http://fakeserver.fakedomain.fake.com"); 307 | 308 | final OAuthClientCredentials clientCredentials = OAuthClientCredentials.newBuilder().clientId("xyz").clientSecret("abc").grantType("client_credentials").authServerURI(new URI("https://my.oauth.club/")).build(); 309 | 310 | when(mockProvider.getClientCredentialsFor(eq(fakeUri))).thenReturn(clientCredentials); 311 | 312 | OAuthToken mockToken = mock(OAuthToken.class); 313 | when(mockToken.getRemainingTime()).thenReturn(50L); 314 | OAuthTokenAttributes fakeOAuthTokenAttributes = OAuthTokenAttributes.newBuilder() 315 | .token(mockToken) 316 | .lock(mockLock) 317 | .build(); 318 | testee.putToken(clientCredentials, fakeOAuthTokenAttributes); 319 | 320 | when(mockLock.tryLock(eq(10L), eq(TimeUnit.SECONDS))).thenReturn(true); 321 | 322 | // test 323 | assertThat(testee.obtainTokenFor(fakeUri), sameInstance((Token) mockToken)); 324 | 325 | verify(mockLock).tryLock(eq(10L), eq(TimeUnit.SECONDS)); 326 | verify(mockLock).unlock(); 327 | } 328 | 329 | @Test 330 | public void testShouldRequestNewTokenIfOldTokenIsExpired() throws Exception { 331 | OAuthToken fakeToken = OAuthToken.newBuilder() 332 | .accessToken("dis_yo_access") 333 | .tokenType("nice") 334 | .expiresIn(-999) 335 | .build(); 336 | 337 | OAuthClientCredentials clientCredentials = OAuthClientCredentials 338 | .newBuilder() 339 | .clientId("xyz") 340 | .clientSecret("abc") 341 | .grantType("client_credentials") 342 | .authServerURI(new URI("https://my.oauth.club/")) 343 | .build(); 344 | 345 | when(mockLock.tryLock(anyLong(), any(TimeUnit.class))).thenReturn(true); 346 | 347 | OAuthTokenAttributes fakeOAuthTokenAttributes = OAuthTokenAttributes.newBuilder() 348 | .token(fakeToken) 349 | .lock(mockLock) 350 | .build(); 351 | 352 | testee.putToken(clientCredentials, fakeOAuthTokenAttributes); 353 | 354 | URI fakeUri = new URI("http://fakeserver.fakedomain.fake.com"); 355 | when(mockProvider.getClientCredentialsFor(eq(fakeUri))).thenReturn(clientCredentials); 356 | 357 | final HttpClient mockClient = mock(HttpClient.class); 358 | when(mockPool.getHttpClient()).thenReturn(mockClient); 359 | 360 | final HttpResponse mockHttpResponse = mock(HttpResponse.class); 361 | when(mockClient.execute(any(HttpPost.class))).thenReturn(mockHttpResponse); 362 | 363 | final StatusLine mockStatusLine = mock(StatusLine.class); 364 | when(mockHttpResponse.getStatusLine()).thenReturn(mockStatusLine); 365 | when(mockStatusLine.getStatusCode()).thenReturn(200); 366 | when(mockHttpResponse.getEntity()).thenReturn(new StringEntity("{\n" + 367 | " \"access_token\": \"sparkpost-token\",\n" + 368 | " \"token_type\": \"Bearer\",\n" + 369 | " \"expires_in\": 60\n" + 370 | "}")); 371 | 372 | final Token token = testee.obtainTokenFor(fakeUri); 373 | assertThat(token.getValue(), is("sparkpost-token")); 374 | 375 | OAuthTokenAttributes testeeOAuthTokenAttributes = testee.getTokenCache().get(clientCredentials); 376 | assertThat(testeeOAuthTokenAttributes.getToken(), sameInstance(token)); 377 | 378 | assertThat(testee.getTokenCache().size(), is(equalTo(1))); 379 | verify(mockPool).getHttpClient(); 380 | } 381 | 382 | @Test 383 | public void testThrowsExceptionWhenGlobalLockThrowsInterruptedException() throws Exception { 384 | final Lock mockGlobalLock = mock(Lock.class); 385 | testee.setLock(mockGlobalLock); 386 | 387 | final InterruptedException mockException = mock(InterruptedException.class); 388 | when(mockGlobalLock.tryLock(eq(10L), eq(TimeUnit.SECONDS))).thenThrow(mockException); 389 | 390 | URI fakeUri = new URI("http://fakeserver.fakedomain.fake.com"); 391 | try { 392 | testee.obtainTokenFor(fakeUri); 393 | fail(); 394 | } catch (IOException e) { 395 | assertThat(e.getMessage(), is(equalTo("error acquiring global lock"))); 396 | assertThat(e.getCause(), sameInstance((Throwable) mockException)); 397 | } 398 | } 399 | 400 | @Test 401 | public void testThrowsExceptionWhenCantGetGlobalLockInTime() throws Exception { 402 | final Lock mockGlobalLock = mock(Lock.class); 403 | when(mockGlobalLock.tryLock(eq(10L), eq(TimeUnit.SECONDS))).thenReturn(false); 404 | testee.setLock(mockGlobalLock); 405 | 406 | URI fakeUri = new URI("http://fakeserver.fakedomain.fake.com"); 407 | 408 | try { 409 | testee.obtainTokenFor(fakeUri); 410 | fail(); 411 | } catch (IOException e) { 412 | assertThat(e.getMessage(), is(equalTo("failed to acquire global lock in time"))); 413 | } 414 | } 415 | 416 | @Test 417 | public void testThrowsExceptionWhenIndividualLockThrowsInterruptedException() throws Exception { 418 | final InterruptedException mockException = mock(InterruptedException.class); 419 | when(mockLock.tryLock(eq(10L), eq(TimeUnit.SECONDS))).thenThrow(mockException); 420 | 421 | final OAuthClientCredentials clientCredentials = OAuthClientCredentials 422 | .newBuilder() 423 | .clientId("xyz") 424 | .clientSecret("abc") 425 | .grantType("client_credentials") 426 | .authServerURI(new URI("https://my.oauth.club/")) 427 | .build(); 428 | 429 | final OAuthTokenAttributes fakeOAuthTokenAttributes = OAuthTokenAttributes.newBuilder() 430 | .lock(mockLock) 431 | .build(); 432 | 433 | testee.putToken(clientCredentials, fakeOAuthTokenAttributes); 434 | 435 | URI fakeUri = new URI("http://fakeserver.fakedomain.fake.com"); 436 | when(mockProvider.getClientCredentialsFor(eq(fakeUri))).thenReturn(clientCredentials); 437 | 438 | try { 439 | testee.obtainTokenFor(fakeUri); 440 | fail(); 441 | } catch (IOException e) { 442 | assertThat(e.getMessage(), is(equalTo("error acquiring lock for https://my.oauth.club/"))); 443 | assertThat(e.getCause(), sameInstance((Throwable) mockException)); 444 | } 445 | } 446 | 447 | @Test 448 | public void testThrowsExceptionWhenCantGetIndividualLockInTime() throws Exception { 449 | when(mockLock.tryLock(eq(10L), eq(TimeUnit.SECONDS))).thenReturn(false); 450 | 451 | final OAuthClientCredentials clientCredentials = OAuthClientCredentials 452 | .newBuilder() 453 | .clientId("xyz") 454 | .clientSecret("abc") 455 | .grantType("client_credentials") 456 | .authServerURI(new URI("https://my.oauth.club/")) 457 | .build(); 458 | 459 | final OAuthTokenAttributes fakeOAuthTokenAttributes = OAuthTokenAttributes.newBuilder() 460 | .lock(mockLock) 461 | .build(); 462 | 463 | testee.putToken(clientCredentials, fakeOAuthTokenAttributes); 464 | 465 | URI fakeUri = new URI("http://fakeserver.fakedomain.fake.com"); 466 | when(mockProvider.getClientCredentialsFor(eq(fakeUri))).thenReturn(clientCredentials); 467 | 468 | try { 469 | testee.obtainTokenFor(fakeUri); 470 | fail(); 471 | } catch (IOException e) { 472 | assertThat(e.getCause().getMessage(), is(equalTo("failed to acquire lock in time for https://my.oauth.club/"))); 473 | } 474 | } 475 | 476 | @Test 477 | public void testExecutorExceptionWhenGettingPreFetch() throws Exception { 478 | final Future mockJob = mock(Future.class); 479 | final ExecutionException mockException = mock(ExecutionException.class); 480 | when(mockJob.get()).thenThrow(mockException); 481 | 482 | final OAuthClientCredentials clientCredentials = OAuthClientCredentials 483 | .newBuilder() 484 | .clientId("xyz") 485 | .clientSecret("abc") 486 | .grantType("client_credentials") 487 | .authServerURI(new URI("https://my.oauth.club/")) 488 | .build(); 489 | 490 | when(mockLock.tryLock(anyLong(), any(TimeUnit.class))).thenReturn(true); 491 | 492 | final OAuthTokenAttributes fakeOAuthTokenAttributes = OAuthTokenAttributes.newBuilder() 493 | .lock(mockLock) 494 | .job(mockJob) 495 | .build(); 496 | 497 | testee.putToken(clientCredentials, fakeOAuthTokenAttributes); 498 | 499 | URI fakeUri = new URI("http://fakeserver.fakedomain.fake.com"); 500 | when(mockProvider.getClientCredentialsFor(eq(fakeUri))).thenReturn(clientCredentials); 501 | 502 | try { 503 | testee.obtainTokenFor(fakeUri); 504 | fail(); 505 | } catch (IOException e) { 506 | assertThat(e.getMessage(), is(equalTo("error requesting oauth token"))); 507 | assertThat(e.getCause(), sameInstance((Throwable) mockException)); 508 | } 509 | } 510 | 511 | @Test 512 | public void testShouldUsePrefetchIfThereIsOneInProgress() throws Exception { 513 | final Future mockJob = mock(Future.class); 514 | final OAuthToken mockNewToken = mock(OAuthToken.class); 515 | when(mockJob.get()).thenReturn(mockNewToken); 516 | when(mockNewToken.getRemainingTime()).thenReturn(100L); 517 | 518 | final OAuthToken fakeToken = OAuthToken.newBuilder() 519 | .expiresIn(-999) 520 | .build(); 521 | 522 | final OAuthClientCredentials clientCredentials = OAuthClientCredentials 523 | .newBuilder() 524 | .authServerURI(new URI("https://my.oauth.club/")) 525 | .build(); 526 | 527 | when(mockLock.tryLock(anyLong(), any(TimeUnit.class))).thenReturn(true); 528 | 529 | final OAuthTokenAttributes fakeOAuthTokenAttributes = OAuthTokenAttributes.newBuilder() 530 | .token(fakeToken) 531 | .lock(mockLock) 532 | .job(mockJob) 533 | .build(); 534 | 535 | testee.putToken(clientCredentials, fakeOAuthTokenAttributes); 536 | 537 | final URI fakeUri = new URI("http://fakeserver.fakedomain.fake.com"); 538 | when(mockProvider.getClientCredentialsFor(eq(fakeUri))).thenReturn(clientCredentials); 539 | 540 | assertThat(testee.obtainTokenFor(fakeUri), sameInstance((Token) mockNewToken)); 541 | verify(mockJob).get(); 542 | } 543 | 544 | @Test 545 | public void testShouldNotDoAnotherPrefetchIfThereIsOneInProgress() throws Exception { 546 | final ExecutorService mockExecutorService = mock(ExecutorService.class); 547 | 548 | testee.setExecutorService(mockExecutorService); 549 | 550 | final Future mockFuture = mock(Future.class); 551 | when(mockExecutorService.submit(any(Callable.class))).thenReturn(mockFuture); 552 | 553 | URI fakeUri = new URI("http://fakeserver.fakedomain.fake.com"); 554 | 555 | final OAuthClientCredentials clientCredentials = OAuthClientCredentials.newBuilder().clientId("xyz").clientSecret("abc").grantType("client_credentials").authServerURI(new URI("https://my.oauth.club/")).build(); 556 | 557 | when(mockProvider.getClientCredentialsFor(eq(fakeUri))).thenReturn(clientCredentials); 558 | 559 | OAuthToken mockToken = Mockito.mock(OAuthToken.class); 560 | when(mockToken.getRemainingTime()).thenReturn(5L); 561 | 562 | when(mockLock.tryLock(anyLong(), any(TimeUnit.class))).thenReturn(true); 563 | 564 | Future mockJob = mock(Future.class); 565 | OAuthTokenAttributes fakeOAuthTokenAttributes = OAuthTokenAttributes.newBuilder() 566 | .token(mockToken) 567 | .job(mockJob) 568 | .lock(mockLock) 569 | .build(); 570 | testee.putToken(clientCredentials, fakeOAuthTokenAttributes); 571 | 572 | when(mockLock.tryLock(eq(10L), eq(TimeUnit.SECONDS))).thenReturn(true); 573 | 574 | // test 575 | testee.obtainTokenFor(fakeUri); 576 | verifyNoMoreInteractions(mockExecutorService); 577 | } 578 | 579 | @Test 580 | public void testShouldDoAnInlineIfThePrefetchTokenHasAlreadyExpired() throws Exception { 581 | final OAuthToken fakeExistingExpiredToken = OAuthToken.newBuilder() 582 | .expiresIn(-999) 583 | .build(); 584 | 585 | final OAuthToken fakePrefetchExpiredToken = OAuthToken.newBuilder() 586 | .expiresIn(-999) 587 | .build(); 588 | 589 | final OAuthClientCredentials clientCredentials = OAuthClientCredentials 590 | .newBuilder() 591 | .clientId("xyz") 592 | .clientSecret("abc") 593 | .grantType("client_credentials") 594 | .authServerURI(new URI("https://my.oauth.club/")) 595 | .build(); 596 | 597 | final Future mockJob = mock(Future.class); 598 | when(mockJob.get()).thenReturn(fakePrefetchExpiredToken); 599 | 600 | when(mockLock.tryLock(anyLong(), any(TimeUnit.class))).thenReturn(true); 601 | 602 | final OAuthTokenAttributes fakeOAuthTokenAttributes = OAuthTokenAttributes.newBuilder() 603 | .token(fakeExistingExpiredToken) 604 | .lock(mockLock) 605 | .job(mockJob) 606 | .build(); 607 | 608 | // set expired token and expired prefetch 609 | testee.putToken(clientCredentials, fakeOAuthTokenAttributes); 610 | 611 | final ExecutorService mockExecutorService = mock(ExecutorService.class); 612 | testee.setExecutorService(mockExecutorService); 613 | 614 | // expect inline prefetch 615 | final HttpClient mockClient = mock(HttpClient.class); 616 | when(mockPool.getHttpClient()).thenReturn(mockClient); 617 | 618 | final HttpResponse mockHttpResponse = mock(HttpResponse.class); 619 | when(mockClient.execute(any(HttpPost.class))).thenReturn(mockHttpResponse); 620 | 621 | final URI fakeUri = new URI("http://fakeserver.fakedomain.fake.com"); 622 | when(mockProvider.getClientCredentialsFor(eq(fakeUri))).thenReturn(clientCredentials); 623 | 624 | final StatusLine mockStatusLine = mock(StatusLine.class); 625 | when(mockHttpResponse.getStatusLine()).thenReturn(mockStatusLine); 626 | when(mockStatusLine.getStatusCode()).thenReturn(200); 627 | when(mockHttpResponse.getEntity()).thenReturn(new StringEntity("{\n" + 628 | " \"access_token\": \"new token request\",\n" + 629 | " \"token_type\": \"Bearer\",\n" + 630 | " \"expires_in\": 100\n" + 631 | "}")); 632 | 633 | final Token token = testee.obtainTokenFor(fakeUri); 634 | assertThat(token.getValue(), is("new token request")); 635 | } 636 | 637 | @Test 638 | public void testObtainTokenThrowsClientSecretException() throws Exception { 639 | testee.setObjectMapper(new ObjectMapper()); 640 | 641 | final HttpClient mockClient = mock(HttpClient.class); 642 | when(mockPool.getHttpClient()).thenReturn(mockClient); 643 | 644 | 645 | final OAuthClientCredentials clientCredentials = OAuthClientCredentials.newBuilder().clientId("xyz").clientSecret("client_credentials").grantType("client_credentials").authServerURI(new URI("https://my.oauth.club/")).build(); 646 | when(mockProvider.getClientCredentialsFor(any(URI.class))).thenReturn(clientCredentials); 647 | 648 | final ClientSecretException clientSecretException = new ClientSecretException("not found"); 649 | when(mockClientSecretService.obtainClientSecret(eq(clientCredentials))).thenThrow(clientSecretException); 650 | 651 | final URI uri = new URI("https://my.service.to.be.authorised.com/"); 652 | 653 | try { 654 | testee.obtainTokenFor(uri); 655 | TestCase.fail("should not reach here"); 656 | } catch (IOException e) { 657 | assertThat(e.getMessage(), is(equalTo("error obtaining client secret"))); 658 | assertThat(e.getCause(), sameInstance((Throwable) clientSecretException)); 659 | } 660 | 661 | } 662 | } 663 | --------------------------------------------------------------------------------