├── .gitignore
├── .travis.yml
├── LICENSE.txt
├── README.md
├── codecov.yml
├── install.sh
├── pom.xml
├── src
├── main
│ └── java
│ │ └── com
│ │ └── authy
│ │ ├── AuthyApiClient.java
│ │ ├── AuthyException.java
│ │ ├── AuthyUtil.java
│ │ ├── OneTouchException.java
│ │ └── api
│ │ ├── ApprovalRequestParams.java
│ │ ├── Error.java
│ │ ├── Formattable.java
│ │ ├── Hash.java
│ │ ├── Instance.java
│ │ ├── JSONBody.java
│ │ ├── Logo.java
│ │ ├── OneTouch.java
│ │ ├── OneTouchResponse.java
│ │ ├── Params.java
│ │ ├── PhoneInfo.java
│ │ ├── PhoneInfoResponse.java
│ │ ├── PhoneVerification.java
│ │ ├── Resource.java
│ │ ├── Token.java
│ │ ├── Tokens.java
│ │ ├── User.java
│ │ ├── UserStatus.java
│ │ ├── Users.java
│ │ └── Verification.java
└── test
│ └── java
│ └── com
│ └── authy
│ ├── TestAuthyUtil.java
│ └── api
│ ├── TestApiBase.java
│ ├── TestOneTouch.java
│ ├── TestPhoneInfo.java
│ ├── TestPhoneVerification.java
│ ├── TestUsers.java
│ ├── TokensTest.java
│ └── UserStatusTest.java
└── verify-legacy-v1.md
/.gitignore:
--------------------------------------------------------------------------------
1 | *.class
2 | *~
3 | target
4 |
5 | # Package Files #
6 | .settings
7 | .project
8 | .classpath
9 | bin/
10 | build/
11 | doc/
12 |
13 | *.iml
14 | idea/
15 |
16 | ### Android template
17 | # Built application files
18 | *.apk
19 | *.ap_
20 |
21 | # Files for the ART/Dalvik VM
22 | *.dex
23 |
24 | # Java class files
25 |
26 | # Generated files
27 | gen/
28 | out/
29 |
30 | # Gradle files
31 | .gradle/
32 |
33 | # Local configuration file (sdk path, etc)
34 | local.properties
35 |
36 | # Proguard folder generated by Eclipse
37 | proguard/
38 |
39 | # Log Files
40 | *.log
41 |
42 | # Android Studio Navigation editor temp files
43 | .navigation/
44 |
45 | # Android Studio captures folder
46 | captures/
47 |
48 | # Intellij
49 | *.iml
50 | .idea/
51 | # Keystore files
52 | *.jks
53 |
54 | # External native build folder generated in Android Studio 2.2 and later
55 | .externalNativeBuild
56 | ### macOS template
57 | *.DS_Store
58 | .AppleDouble
59 | .LSOverride
60 |
61 | # Icon must end with two \r
62 | Icon
63 |
64 |
65 | # Thumbnails
66 | ._*
67 |
68 | # Files that might appear in the root of a volume
69 | .DocumentRevisions-V100
70 | .fseventsd
71 | .Spotlight-V100
72 | .TemporaryItems
73 | .Trashes
74 | .VolumeIcon.icns
75 | .com.apple.timemachine.donotpresent
76 |
77 | # Directories potentially created on remote AFP share
78 | .AppleDB
79 | .AppleDesktop
80 | Network Trash Folder
81 | Temporary Items
82 | .apdisk
83 | ### JetBrains template
84 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
85 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
86 |
87 | # User-specific stuff:
88 |
89 | # Sensitive or high-churn files:
90 | .idea/dataSources/
91 | .idea/dataSources.ids
92 | .idea/dataSources.xml
93 | .idea/dataSources.local.xml
94 | .idea/sqlDataSources.xml
95 | .idea/dynamic.xml
96 | .idea/uiDesigner.xml
97 |
98 | # Gradle:
99 | .idea/gradle.xml
100 |
101 | # Mongo Explorer plugin:
102 | .idea/mongoSettings.xml
103 |
104 | ## File-based project format:
105 | *.iws
106 |
107 | ## Plugin-specific files:
108 |
109 | # IntelliJ
110 | /out/
111 |
112 | # mpeltonen/sbt-idea plugin
113 | .idea_modules/
114 |
115 | # JIRA plugin
116 | atlassian-ide-plugin.xml
117 |
118 | # Crashlytics plugin (for Android Studio and IntelliJ)
119 | com_crashlytics_export_strings.xml
120 | crashlytics.properties
121 | crashlytics-build.properties
122 | fabric.properties
123 | ### Maven template
124 | target/
125 | pom.xml.tag
126 | pom.xml.releaseBackup
127 | pom.xml.versionsBackup
128 | pom.xml.next
129 | release.properties
130 | dependency-reduced-pom.xml
131 | buildNumber.properties
132 | .mvn/timing.properties
133 |
134 | # Exclude maven wrapper
135 | !/.mvn/wrapper/maven-wrapper.jar
136 | ### JetBrains template
137 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
138 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
139 |
140 | # User-specific stuff:
141 | .idea/workspace.xml
142 | .idea/tasks.xml
143 |
144 | # Sensitive or high-churn files:
145 |
146 | # Gradle:
147 | .idea/libraries
148 |
149 | # Mongo Explorer plugin:
150 |
151 | ## File-based project format:
152 |
153 | ## Plugin-specific files:
154 |
155 | # IntelliJ
156 |
157 | # mpeltonen/sbt-idea plugin
158 |
159 | # JIRA plugin
160 |
161 | # Crashlytics plugin (for Android Studio and IntelliJ)
162 | ### macOS template
163 |
164 | # Icon must end with two \r
165 |
166 |
167 | # Thumbnails
168 |
169 | # Files that might appear in the root of a volume
170 |
171 | # Directories potentially created on remote AFP share
172 | ### Windows template
173 | # Windows image file caches
174 | Thumbs.db
175 | ehthumbs.db
176 |
177 | # Folder config file
178 | Desktop.ini
179 |
180 | # Recycle Bin used on file shares
181 | $RECYCLE.BIN/
182 |
183 | # Windows Installer files
184 | *.cab
185 | *.msi
186 | *.msm
187 | *.msp
188 |
189 | # Windows shortcuts
190 | *.lnk
191 | ### Java template
192 |
193 | # BlueJ files
194 | *.ctxt
195 |
196 | # Mobile Tools for Java (J2ME)
197 | .mtj.tmp/
198 |
199 | # Package Files #
200 | *.jar
201 | *.war
202 | *.ear
203 |
204 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
205 | hs_err_pid*
206 | ### Eclipse template
207 |
208 | .metadata
209 | tmp/
210 | *.tmp
211 | *.bak
212 | *.swp
213 | *~.nib
214 | .settings/
215 | .loadpath
216 | .recommenders
217 |
218 | # Eclipse Core
219 |
220 | # External tool builders
221 | .externalToolBuilders/
222 |
223 | # Locally stored "Eclipse launch configurations"
224 | *.launch
225 |
226 | # PyDev specific (Python IDE for Eclipse)
227 | *.pydevproject
228 |
229 | # CDT-specific (C/C++ Development Tooling)
230 | .cproject
231 |
232 | # JDT-specific (Eclipse Java Development Tools)
233 |
234 | # Java annotation processor (APT)
235 | .factorypath
236 |
237 | # PDT-specific (PHP Development Tools)
238 | .buildpath
239 |
240 | # sbteclipse plugin
241 | .target
242 |
243 | # Tern plugin
244 | .tern-project
245 |
246 | # TeXlipse plugin
247 | .texlipse
248 |
249 | # STS (Spring Tool Suite)
250 | .springBeans
251 |
252 | # Code Recommenders
253 | .recommenders/
254 | ### Maven template
255 |
256 | # Exclude maven wrapper
257 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: java
2 | jdk:
3 | - openjdk8
4 |
5 | script: "mvn cobertura:cobertura"
6 |
7 | after_success:
8 | - bash <(curl -s https://codecov.io/bash)
9 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Copyright (c) 2011-2020 Authy Inc
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining
4 | a copy of this software and associated documentation files (the
5 | "Software"), to deal in the Software without restriction, including
6 | without limitation the rights to use, copy, modify, merge, publish,
7 | distribute, sublicense, and/or sell copies of the Software, and to
8 | permit persons to whom the Software is furnished to do so, subject to
9 | the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be
12 | included in all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://travis-ci.org/twilio/authy-java)
2 | [](https://codecov.io/gh/twilio/authy-java)
3 |
4 | 🚨🚨🚨
5 |
6 | **This library is no longer actively maintained.** The Authy API has been replaced with the [Twilio Verify API](https://www.twilio.com/docs/verify). Twilio will support the Authy API through November 1, 2022 for SMS/Voice. After this date, we’ll start to deprecate the service for SMS/Voice. Any requests sent to the API after May 1, 2023, will automatically receive an error. Push and TOTP will continue to be supported through July 2023.
7 |
8 | [Learn more about migrating from Authy to Verify.](https://www.twilio.com/blog/migrate-authy-to-verify)
9 |
10 | Please visit the Twilio Docs for:
11 | * [Verify + Java (Servlets) quickstart](https://www.twilio.com/docs/verify/quickstarts/java-servlets)
12 | * [Twilio Java helper library](https://www.twilio.com/docs/libraries/java)
13 | * [Verify API reference](https://www.twilio.com/docs/verify/api)
14 |
15 | Please direct any questions to [Twilio Support](https://support.twilio.com/hc/en-us). Thank you!
16 |
17 | 🚨🚨🚨
18 |
19 |
20 | ## Java Client for Twilio Authy Two-Factor Authentication (2FA) API
21 |
22 | Documentation for Java usage of the Authy API lives in the [official Twilio documentation](https://www.twilio.com/docs/authy/api/).
23 |
24 | The Authy API supports multiple channels of 2FA:
25 | * One-time passwords via SMS and voice.
26 | * Soft token ([TOTP](https://www.twilio.com/docs/glossary/totp) via the Authy App)
27 | * Push authentication via the Authy App
28 |
29 | If you only need SMS and Voice support for one-time passwords, we recommend using the [Twilio Verify API](https://www.twilio.com/docs/verify/api) instead.
30 |
31 | [More on how to choose between Authy and Verify here.](https://www.twilio.com/docs/verify/authy-vs-verify)
32 |
33 | ### Authy Quickstart
34 |
35 | For a full tutorial, check out either of the Java Authy Quickstarts in our docs:
36 | * [Java/Spring Authy Quickstart](https://www.twilio.com/docs/authy/quickstart/two-factor-authentication-java-spring)
37 | * [Java/Servlets Authy Quickstart](https://www.twilio.com/docs/authy/quickstart/two-factor-authentication-java-servlets)
38 |
39 | ## Authy Java Installation
40 |
41 | ### Dependencies
42 | This project uses [json.org](https://github.com/douglascrockford/JSON-java), you can find
43 | the [json.org jar versions here](https://search.maven.org/#search|gav|1|g%3A%22org.json%22%20AND%20a%3A%22json%22)
44 |
45 | * **Ant:** no need to include json.org since ant already includes it in the final jar.
46 | * **Maven:** need to include the [json.org jar](https://search.maven.org/#search|gav|1|g%3A%22org.json%22%20AND%20a%3A%22json%22) in your jar external libraries.
47 |
48 | ## Usage
49 |
50 | Add the library to the project by putting it in the dependencies section of your `pom.xml`:
51 | ```
52 |
53 |
54 | com.authy
55 | authy-java
56 | 1.5.0
57 |
58 | ```
59 |
60 | To use the Authy client, import the API and initialize it with your production API Key found in the [Twilio Console](https://www.twilio.com/console/authy/applications/):
61 |
62 | ```java
63 | import com.authy.*;
64 | import com.authy.api.*;
65 |
66 | AuthyApiClient client = new AuthyApiClient("your-api-key")
67 | ```
68 |
69 | 
70 |
71 | ## 2FA Workflow
72 |
73 | 1. [Create a user](https://www.twilio.com/docs/authy/api/users#enabling-new-user)
74 | 2. [Send a one-time password](https://www.twilio.com/docs/authy/api/one-time-passwords)
75 | 3. [Verify a one-time password](https://www.twilio.com/docs/authy/api/one-time-passwords#verify-a-one-time-password)
76 |
77 | **OR**
78 |
79 | 1. [Create a user](https://www.twilio.com/docs/authy/api/users#enabling-new-user)
80 | 2. [Send a push authentication](https://www.twilio.com/docs/authy/api/push-authentications)
81 | 3. [Check a push authentication status](https://www.twilio.com/docs/authy/api/push-authentications#check-approval-request-status)
82 |
83 |
84 | ## Phone Verification
85 |
86 | [Phone verification now lives in the Twilio API](https://www.twilio.com/docs/verify/api) and has [Java support through the official Twilio helper libraries](https://www.twilio.com/docs/libraries/java).
87 |
88 | [Legacy (V1) documentation here.](verify-legacy-v1.md) **Verify V1 is not recommended for new development. Please consider using [Verify V2](https://www.twilio.com/docs/verify/api)**.
89 |
90 | ## Copyright
91 |
92 | Copyright (c) 2011-2020 Authy Inc. See [LICENSE](https://github.com/twilio/authy-java/blob/master/LICENSE.txt) for further details.
93 |
--------------------------------------------------------------------------------
/codecov.yml:
--------------------------------------------------------------------------------
1 | comment:
2 | layout: "reach, diff, flags, files"
3 | behavior: default
4 | require_changes: false # if true: only post the comment if coverage changes
5 | require_base: no # [yes :: must have a base report to post]
6 | require_head: yes # [yes :: must have a head report to post]
7 |
--------------------------------------------------------------------------------
/install.sh:
--------------------------------------------------------------------------------
1 | function install(){
2 | mvn install:install-file -Dfile=$1 -DgroupId=$2 -DartifactId=$3 -Dversion=$4 -Dpackaging=jar
3 | }
4 |
5 | install dist/authy-java.jar com.authy authy-java 1.0
6 |
7 | #
8 | # com.authy
9 | # authy-java
10 | # 1.0
11 | #
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 | com.authy
6 | authy-java
7 | 1.5.1
8 | jar
9 | Authy Java
10 | Java library to access the Authy API.
11 | https://github.com/authy/authy-java
12 |
13 |
14 |
15 | The MIT License (MIT)
16 | https://github.com/authy/authy-java/blob/master/LICENSE.txt
17 |
18 |
19 |
20 |
21 |
22 | Sergio Aristizabal
23 | saristizabal@twilio.com
24 | Twilio
25 | http://www.twilio.com
26 |
27 |
28 |
29 |
30 | UTF-8
31 |
32 |
33 |
34 | scm:git:git@github.com:authy/authy-java.git
35 | scm:git:git@github.com:authy/authy-java.git
36 | git@github.com:authy/authy-java.git
37 |
38 |
39 |
40 |
41 | org.json
42 | json
43 | 20150729
44 |
45 |
46 | junit
47 | junit
48 | 4.11
49 | test
50 |
51 |
52 | com.github.tomakehurst
53 | wiremock
54 | 2.11.0
55 | test
56 |
57 |
58 |
59 |
60 |
61 | ossrh
62 | Authy Maven Snapshot Repository
63 |
64 | https://oss.sonatype.org/content/repositories/snapshots/
65 |
66 |
67 |
68 |
69 | ossrh
70 | Authy Maven Repository
71 |
72 | https://oss.sonatype.org/service/local/staging/deploy/maven2/
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 | release
81 |
82 |
83 | performRelease
84 | true
85 |
86 |
87 |
88 |
89 |
90 | org.apache.maven.plugins
91 | maven-gpg-plugin
92 | 1.5
93 |
94 |
95 | sign-artifacts
96 | verify
97 |
98 | sign
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 | org.apache.maven.plugins
111 | maven-compiler-plugin
112 | 3.1
113 |
114 | 1.8
115 | 1.8
116 |
117 |
118 |
119 |
120 | org.apache.maven.plugins
121 | maven-release-plugin
122 |
123 | true
124 | false
125 |
126 | release
127 | deploy
128 |
129 |
130 |
131 |
132 | org.apache.maven.plugins
133 | maven-source-plugin
134 | 2.2.1
135 |
136 |
137 | attach-sources
138 |
139 | jar
140 |
141 |
142 |
143 |
144 |
145 | org.apache.maven.plugins
146 | maven-javadoc-plugin
147 | 2.9.1
148 |
149 |
150 | attach-javadocs
151 |
152 | jar
153 |
154 |
155 |
156 |
157 |
158 |
159 | org.sonatype.plugins
160 | nexus-staging-maven-plugin
161 | 1.6.3
162 | true
163 |
164 | ossrh
165 | https://oss.sonatype.org/
166 | true
167 |
168 |
169 |
170 | org.codehaus.mojo
171 | cobertura-maven-plugin
172 | 2.7
173 |
174 |
175 | html
176 | xml
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
--------------------------------------------------------------------------------
/src/main/java/com/authy/AuthyApiClient.java:
--------------------------------------------------------------------------------
1 | package com.authy;
2 |
3 | import com.authy.api.*;
4 |
5 | /**
6 | * @author Julian Camargo
7 | *
8 | * Copyright © 2017 Twilio, Inc. All Rights Reserved.
9 | */
10 | public class AuthyApiClient {
11 | public static final String CLIENT_NAME = "AuthyJava";
12 | public static final String DEFAULT_API_URI = "https://api.authy.com";
13 | public static final String VERSION = "1.5.0";
14 | private Users users;
15 | private Tokens tokens;
16 | private String apiUri, apiKey;
17 | private PhoneVerification phoneVerification;
18 | private PhoneInfo phoneInfo;
19 | private OneTouch oneTouch;
20 |
21 |
22 | public AuthyApiClient(String apiKey, String apiUri) {
23 | init(apiKey, apiUri, false);
24 | }
25 |
26 | public AuthyApiClient(String apiKey) {
27 | init(apiKey, DEFAULT_API_URI, false);
28 | }
29 |
30 | public AuthyApiClient(String apiKey, String apiUri, boolean testFlag) {
31 | init(apiKey, apiUri, testFlag);
32 | }
33 |
34 | private void init(String apiKey, String apiUrl, boolean testFlag) {
35 | this.apiUri = apiUrl;
36 | this.apiKey = apiKey;
37 |
38 | this.phoneInfo = new PhoneInfo(this.apiUri, this.apiKey, testFlag);
39 | this.phoneVerification = new PhoneVerification(this.apiUri, this.apiKey, testFlag);
40 | this.users = new Users(this.apiUri, this.apiKey, testFlag);
41 | this.tokens = new Tokens(this.apiUri, this.apiKey, testFlag);
42 | this.oneTouch = new OneTouch(this.apiUri, this.apiKey, testFlag);
43 | }
44 |
45 | public Users getUsers() {
46 | return this.users;
47 | }
48 |
49 | public Tokens getTokens() {
50 | return this.tokens;
51 | }
52 |
53 | public PhoneVerification getPhoneVerification() {
54 | return this.phoneVerification;
55 | }
56 |
57 | public PhoneInfo getPhoneInfo() {
58 | return this.phoneInfo;
59 | }
60 |
61 | public OneTouch getOneTouch() {
62 | return oneTouch;
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/main/java/com/authy/AuthyException.java:
--------------------------------------------------------------------------------
1 | package com.authy;
2 |
3 | import com.authy.api.Error;
4 |
5 | /**
6 | * @author Julian Camargo
7 | *
8 | * Copyright © 2017 Twilio, Inc. All Rights Reserved.
9 | */
10 | public class AuthyException extends Exception {
11 |
12 | private final Integer status;
13 | private final Error.Code errorCode;
14 |
15 | public AuthyException(String message, Throwable throwable, Integer status) {
16 | super(message, throwable);
17 | this.status = status;
18 | this.errorCode = null;
19 | }
20 |
21 | public AuthyException(String message, Throwable throwable, Integer status, Error.Code errorCode) {
22 | super(message, throwable);
23 | this.status = status;
24 | this.errorCode = errorCode;
25 | }
26 |
27 | public AuthyException(String message, Integer status, Error.Code errorCode) {
28 | super(message);
29 | this.status = status;
30 | this.errorCode = errorCode;
31 | }
32 |
33 | public AuthyException(String message) {
34 | super(message);
35 | this.status = null;
36 | this.errorCode = null;
37 | }
38 |
39 | public AuthyException(String message, Throwable throwable) {
40 | this(message, throwable, null, null);
41 | }
42 |
43 | public int getStatus() {
44 | return status;
45 | }
46 |
47 | public Error.Code getErrorCode() {
48 | return errorCode;
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/main/java/com/authy/AuthyUtil.java:
--------------------------------------------------------------------------------
1 | package com.authy;
2 |
3 | import org.json.JSONArray;
4 | import org.json.JSONObject;
5 |
6 | import javax.crypto.Mac;
7 | import javax.crypto.spec.SecretKeySpec;
8 | import javax.xml.bind.DatatypeConverter;
9 | import java.io.IOException;
10 | import java.io.InputStream;
11 | import java.io.UnsupportedEncodingException;
12 | import java.net.URLEncoder;
13 | import java.util.*;
14 | import java.util.logging.Level;
15 | import java.util.logging.Logger;
16 |
17 | import static java.nio.charset.StandardCharsets.UTF_8;
18 |
19 | /**
20 | * @author hansospina
21 | *
22 | * Copyright © 2017 Twilio, Inc. All Rights Reserved.
23 | */
24 | public class AuthyUtil {
25 | public static final String HEADER_AUTHY_SIGNATURE_NONCE = "X-Authy-Signature-Nonce";
26 | public static final String HEADER_AUTHY_SIGNATURE = "X-Authy-Signature";
27 |
28 | private static final Logger LOGGER = Logger.getLogger(AuthyUtil.class.getName());
29 |
30 | private static String hmacSha(String KEY, String VALUE) throws OneTouchException {
31 |
32 | try {
33 | SecretKeySpec signingKey = new SecretKeySpec(KEY.getBytes(UTF_8), "HmacSHA256");
34 | Mac mac = Mac.getInstance("HmacSHA256");
35 | mac.init(signingKey);
36 | byte[] rawHmac = mac.doFinal(VALUE.getBytes(UTF_8));
37 | return DatatypeConverter.printBase64Binary(rawHmac);
38 | } catch (Exception ex) {
39 | // capture the exceptions and wrap them using authy.
40 | throw new OneTouchException("There was an exception checking the Authy OneTouch signature.", ex);
41 | }
42 | }
43 |
44 |
45 | /**
46 | * Validates the request information to
47 | *
48 | * @param parameters The request parameters(all of them)
49 | * @param headers The headers of the request
50 | * @param url The url of the request.
51 | * @param apiKey the security token from the authy library
52 | * @return true if the signature ios valid, false otherwise
53 | * @throws UnsupportedEncodingException if the string parameters have problems with UTF-8 encoding.
54 | */
55 | private static boolean validateSignature(Map parameters, Map headers, String method, String url, String apiKey) throws OneTouchException, UnsupportedEncodingException {
56 |
57 | if (headers == null)
58 | throw new OneTouchException("No headers sent");
59 |
60 | if (!headers.containsKey(HEADER_AUTHY_SIGNATURE))
61 | throw new OneTouchException("'SIGNATURE' is missing.");
62 |
63 | if (!headers.containsKey(HEADER_AUTHY_SIGNATURE_NONCE))
64 | throw new OneTouchException("'NONCE' is missing.");
65 |
66 | if (parameters == null || parameters.isEmpty())
67 | throw new OneTouchException("'PARAMS' are missing.");
68 |
69 | StringBuilder sb = new StringBuilder(headers.get(HEADER_AUTHY_SIGNATURE_NONCE))
70 | .append("|")
71 | .append(method)
72 | .append("|")
73 | .append(url)
74 | .append("|")
75 | .append(mapToQuery(parameters));
76 |
77 | String signature = hmacSha(apiKey, sb.toString());
78 |
79 | // let's check that the Authy signature is valid
80 | return signature.equals(headers.get(HEADER_AUTHY_SIGNATURE));
81 | }
82 |
83 |
84 | public static void extract(String pre, JSONObject obj, HashMap map) {
85 |
86 | for (String k : obj.keySet()) {
87 |
88 | String key = pre.length() == 0 ? k : pre + "[" + k + "]";
89 | if (obj.optJSONObject(k) != null) {
90 | extract(key, obj.getJSONObject(k), map);
91 |
92 | } else if (obj.optJSONArray(k) != null) {
93 |
94 |
95 | JSONArray arr = obj.getJSONArray(k);
96 |
97 | int i = 0;
98 |
99 | for (Object tmp : arr) {
100 | String tmpKey = key + "[" + i + "]";
101 |
102 | if (tmp instanceof JSONObject) {
103 | extract(tmpKey, (JSONObject) tmp, map);
104 | } else {
105 | map.put(tmpKey, getValue(tmp));
106 | }
107 | i++;
108 | }
109 |
110 | } else {
111 | map.put(key, getValue(obj.get(k)));
112 | }
113 |
114 | }
115 |
116 | }
117 |
118 |
119 | private static String getValue(Object val) {
120 |
121 | if (val instanceof Boolean) {
122 | return Boolean.toString(((Boolean) val));
123 | } else if (val instanceof Integer) {
124 | return Long.toString(((Integer) val));
125 | } else if (val instanceof Long) {
126 | return Long.toString(((Long) val));
127 | } else if (val instanceof Float) {
128 | return Float.toString(((Float) val));
129 | } else if (val instanceof Double) {
130 | return Double.toString(((Double) val));
131 | } else if (JSONObject.NULL.equals(val)) {
132 | return "";
133 | } else {
134 | return String.valueOf(val);
135 | }
136 |
137 | }
138 |
139 | public static String mapToQuery(Map map) throws OneTouchException, UnsupportedEncodingException {
140 |
141 | StringBuilder sb = new StringBuilder();
142 |
143 | SortedSet keys = new TreeSet<>(map.keySet());
144 |
145 | boolean first = true;
146 |
147 | for (String key : keys) {
148 | if (key.length() > 200)
149 | throw new OneTouchException("max number of characters of key exceeded.");
150 |
151 | if (first) {
152 | first = false;
153 | } else {
154 | sb.append("&");
155 | }
156 |
157 | String value = map.get(key);
158 |
159 | // don't encode null values
160 | if (value == null) {
161 | continue;
162 | }
163 |
164 | sb.append(URLEncoder.encode(key.replaceAll("\\[([0-9])*\\]", "[]"), UTF_8.name())).append("=").append(URLEncoder.encode(value, UTF_8.name()));
165 | }
166 |
167 | return sb.toString();
168 |
169 | }
170 |
171 |
172 | /**
173 | * Validates the request information to
174 | *
175 | * @param body The body of the request in case of a POST method
176 | * @param headers The headers of the request
177 | * @param url The url of the request.
178 | * @param apiKey the security token from the authy library
179 | * @return true if the signature ios valid, false otherwise
180 | * @throws com.authy.OneTouchException
181 | * @throws UnsupportedEncodingException if the string parameters have problems with UTF-8 encoding.
182 | */
183 | public static boolean validateSignatureForPost(String body, Map headers, String url, String apiKey) throws OneTouchException, UnsupportedEncodingException {
184 | HashMap params = new HashMap<>();
185 | if (body == null || body.isEmpty())
186 | throw new OneTouchException("'PARAMS' are missing.");
187 | extract("", new JSONObject(body), params);
188 | return validateSignature(params, headers, "POST", url, apiKey);
189 | }
190 |
191 | /**
192 | * Validates the request information to
193 | *
194 | * @param params The query parameters in case of a GET request
195 | * @param headers The headers of the request
196 | * @param url The url of the request.
197 | * @param apiKey the security token from the authy library
198 | * @return true if the signature ios valid, false otherwise
199 | * @throws com.authy.OneTouchException
200 | * @throws UnsupportedEncodingException if the string parameters have problems with UTF-8 encoding.
201 | */
202 | public static boolean validateSignatureForGet(Map params, Map headers, String url, String apiKey) throws OneTouchException, UnsupportedEncodingException {
203 | return validateSignature(params, headers, "GET", url, apiKey);
204 | }
205 |
206 |
207 | /**
208 | * Loads your api_key and api_url properties from the given property file
209 | *
210 | * Two important things to have in mind here:
211 | * 1) if api_key and api_url are defined as environment variables, they will be returned as the properties.
212 | * 2) If you want to load your properties file have in mind your classloader path may change.
213 | *
214 | * @return the Properties object containing the properties to setup Authy or an empty Properties object if no properties were found
215 | */
216 | public static Properties loadProperties(String path, Class cls) {
217 |
218 | Properties properties = new Properties();
219 |
220 | // environment variables will always override properties file
221 |
222 | try {
223 |
224 | InputStream in = cls.getResourceAsStream(path);
225 |
226 | // if we cant find the properties file
227 | if (in != null) {
228 | properties.load(in);
229 | }
230 |
231 |
232 | // Env variables will always override properties
233 | if (System.getenv("api_key") != null && System.getenv("api_url") != null) {
234 | properties.put("api_key", System.getenv("api_key"));
235 | properties.put("api_url", System.getenv("api_url"));
236 | }
237 |
238 | } catch (IOException e) {
239 | LOGGER.log(Level.SEVERE, "Problems loading properties", e);
240 | }
241 |
242 |
243 | return properties;
244 | }
245 |
246 | }
247 |
--------------------------------------------------------------------------------
/src/main/java/com/authy/OneTouchException.java:
--------------------------------------------------------------------------------
1 | package com.authy;
2 |
3 | /**
4 | * @author hansospina
5 | *
6 | * Copyright © 2017 Twilio, Inc. All Rights Reserved.
7 | */
8 | public class OneTouchException extends AuthyException {
9 |
10 | public OneTouchException(String message, Throwable throwable) {
11 | super(message, throwable);
12 | }
13 |
14 | public OneTouchException(String message) {
15 | super(message);
16 | }
17 |
18 | }
19 |
--------------------------------------------------------------------------------
/src/main/java/com/authy/api/ApprovalRequestParams.java:
--------------------------------------------------------------------------------
1 | /*
2 | * To change this license header, choose License Headers in Project Properties.
3 | * To change this template file, choose Tools | Templates
4 | * and open the template in the editor.
5 | */
6 | package com.authy.api;
7 |
8 | import com.authy.OneTouchException;
9 |
10 | import java.util.ArrayList;
11 | import java.util.HashMap;
12 | import java.util.List;
13 |
14 | /**
15 | * @author hansospina
16 | *
17 | * Copyright © 2017 Twilio, Inc. All Rights Reserved.
18 | */
19 | public class ApprovalRequestParams {
20 |
21 | private HashMap details = new HashMap();
22 | private HashMap hidden = new HashMap();
23 | private List logos = new ArrayList<>();
24 | private Long secondsToExpire;
25 | private Integer authyId;
26 | private String message;
27 |
28 | // lock the main constructor
29 | private ApprovalRequestParams() {
30 | }
31 |
32 | public HashMap getDetails() {
33 | return details;
34 | }
35 |
36 | public HashMap getHidden() {
37 | return hidden;
38 | }
39 |
40 | public List getLogos() {
41 | return logos;
42 | }
43 |
44 | public Long getSecondsToExpire() {
45 | return secondsToExpire;
46 | }
47 |
48 | public Integer getAuthyId() {
49 | return authyId;
50 | }
51 |
52 | public String getMessage() {
53 | return message;
54 | }
55 |
56 | public enum Resolution {
57 | Default("default"),
58 | Low("low"),
59 | Medium("med"),
60 | High("high");
61 |
62 | private String res;
63 |
64 | Resolution(String res) {
65 | this.res = res;
66 | }
67 |
68 | public String getRes() {
69 | return res;
70 | }
71 |
72 |
73 | }
74 |
75 | public static class Builder {
76 |
77 |
78 | public static final String MESSAGE_ERROR = "Param message cannot be null or empty and it's length needs to be less than 200 max characters.";
79 | public static final String AUTHYID_ERROR = "Param authyId cannot be null and should be the id of the user that will be authorized using OneTouch.";
80 | public static final String DETAIL_ERROR = "Each entry(key,value) for a detail needs to have not null or empty values and keys and it's lengths cannot exceed 200 max characters.";
81 | public static final String HIDDEN_DETAIL_ERROR = "Each entry(key,value) for a hidden detail needs to have not null or empty values and keys and it's lengths cannot exceed 200 max characters.";
82 | public static final String LOGO_ERROR_RES = "The 'Resolution' for a logo cannot be null.";
83 | public static final String LOGO_ERROR_URL = "The 'url' for a logo cannot be null or empty and it's length needs to be less than 500 max characters.";
84 | public static final String LOGO_ERROR_DEFAULT = "If you provide logos you should always provide the default Resolution.";
85 | private static final int MAXSIZE = 200;
86 | private static final int MAXSIZEURL = 500;
87 | ApprovalRequestParams params = new ApprovalRequestParams();
88 | private HashMap currentLogos = new HashMap<>();
89 |
90 | /**
91 | * This will create a valid builder with the initial required params.
92 | * See: https://www.twilio.com/docs/api/authy/authy-onetouch-api#parameters
93 | *
94 | * @param authyId The id of the user that will be authorized using OneTouch
95 | * @param message The message to show to the user, cannot be null or empty and it's length needs to be less than 200 max characters.
96 | * @throws OneTouchException If any of the params doesn't match the required/length rules.
97 | */
98 | public Builder(Integer authyId, String message) throws OneTouchException {
99 |
100 | if (authyId == null) {
101 | throw new OneTouchException(AUTHYID_ERROR);
102 | }
103 |
104 | if (message == null || message.isEmpty() || message.length() > MAXSIZE) {
105 | throw new OneTouchException(MESSAGE_ERROR);
106 | }
107 |
108 | this.params.authyId = authyId;
109 | this.params.message = message;
110 | }
111 |
112 | /**
113 | * Defines the second to expire parameter
114 | *
115 | * See: https://www.twilio.com/docs/api/authy/authy-onetouch-api#parameters
116 | *
117 | * @param secondsToExpire Number of seconds that the approval request will be available for being responded.
118 | */
119 | public Builder setSecondsToExpire(Long secondsToExpire) {
120 | // this parameter can be null or any long value so we don't need to validate anything else.
121 | this.params.secondsToExpire = secondsToExpire;
122 | return this;
123 | }
124 |
125 | /**
126 | * Adds a key,value pair into the details which is a Dictionary containing the [ApprovalRequest] details that will be shown to user.
127 | *
128 | * See: https://www.twilio.com/docs/api/authy/authy-onetouch-api#parameters
129 | *
130 | * @param key The label of the detail that will be shown to the user, cannot be null or empty and it's length needs to be less than 200 max characters.
131 | * @param value The value of the detail that will be shown to the user, cannot be null or empty and it's length needs to be less than 200 max characters.
132 | * @throws OneTouchException If any of the params doesn't match the required/length rules.
133 | */
134 | public Builder addDetail(String key, String value) throws OneTouchException {
135 |
136 | if (key == null || key.isEmpty() || key.length() > MAXSIZE) {
137 | throw new OneTouchException(DETAIL_ERROR);
138 | }
139 |
140 | if (value == null || value.isEmpty() || value.length() > MAXSIZE) {
141 | throw new OneTouchException(DETAIL_ERROR);
142 | }
143 |
144 | this.params.details.put(key, value);
145 |
146 | return this;
147 | }
148 |
149 | /**
150 | * Adds a key,value pair into the hidden_details which is a Dictionary containing the approval request details hidden to user.
151 | *
152 | * See: https://www.twilio.com/docs/api/authy/authy-onetouch-api#parameters
153 | *
154 | * @param key The label of the hidden detail that will be not shown to the user, cannot be null or empty and it's length needs to be less than 200 max characters.
155 | * @param value The value of the hidden detail that will be not shown to the user, cannot be null or empty and it's length needs to be less than 200 max characters.
156 | * @throws OneTouchException If any of the params doesn't match the required/length rules.
157 | */
158 | public Builder addHiddenDetail(String key, String value) throws OneTouchException {
159 |
160 | if (key == null || key.isEmpty() || key.length() > MAXSIZE) {
161 | throw new OneTouchException(HIDDEN_DETAIL_ERROR);
162 | }
163 |
164 | if (value == null || value.isEmpty() || value.length() > MAXSIZE) {
165 | throw new OneTouchException(HIDDEN_DETAIL_ERROR);
166 | }
167 |
168 | this.params.hidden.put(key, value);
169 |
170 | return this;
171 | }
172 |
173 |
174 | /**
175 | * Adds a new logo item to the Logos array.
176 | *
177 | * @param resolution The Resolution wanted for the given logo(Default, log,med,high)
178 | * @param url The url to be used to load the logo.
179 | * @throws OneTouchException If any of the params doesn't match the required/length rules.
180 | */
181 | public Builder addLogo(Resolution resolution, String url) throws OneTouchException {
182 |
183 | if (resolution == null) {
184 | throw new OneTouchException(LOGO_ERROR_RES);
185 | }
186 |
187 | if (url == null || url.isEmpty() || url.length() > MAXSIZEURL) {
188 | throw new OneTouchException(LOGO_ERROR_URL);
189 | }
190 |
191 | // let's use a map to prevent duplicates in the logo list,
192 | // if a new logo with an already used resolution comes we will just replace it.
193 | this.currentLogos.put(resolution, new Logo(resolution, url));
194 | return this;
195 | }
196 |
197 |
198 | /**
199 | * Compiles and creates the provided set of parameters to have a ready to use ApprovalRequestParams object.
200 | *
201 | * @return The bean containing all the properties required to send a valid OneTouch request to Authy.
202 | * @throws OneTouchException If any of the params doesn't match the required/length rules.
203 | */
204 | public ApprovalRequestParams build() throws OneTouchException {
205 |
206 | // if we have logo but the user didnt send a default this
207 | if (!currentLogos.isEmpty() && !currentLogos.containsKey(Resolution.Default)) {
208 | throw new OneTouchException(LOGO_ERROR_DEFAULT);
209 | }
210 |
211 | this.params.logos.addAll(currentLogos.values());
212 |
213 |
214 | return this.params;
215 | }
216 |
217 |
218 | }
219 |
220 |
221 | }
222 |
--------------------------------------------------------------------------------
/src/main/java/com/authy/api/Error.java:
--------------------------------------------------------------------------------
1 | package com.authy.api;
2 |
3 | import javax.xml.bind.JAXBContext;
4 | import javax.xml.bind.Marshaller;
5 | import javax.xml.bind.annotation.XmlElement;
6 | import javax.xml.bind.annotation.XmlRootElement;
7 | import java.io.StringWriter;
8 | import java.util.HashMap;
9 | import java.util.Map;
10 |
11 | @XmlRootElement(name = "errors")
12 | public class Error implements Formattable {
13 |
14 | public enum Code {
15 | DEFAULT_ERROR(6000),
16 | API_KEY_INVALID(60001),
17 | INVALID_REQUEST(60002),
18 | INVALID_PARAMETER(60004),
19 | INVALID_ENCODING(60005),
20 | TOKEN_VIA_CALL_ADDON_DISABLED(60006),
21 | SMS_DISABLED(60007),
22 | ACCOUNT_SUSPENDED(60008),
23 | MONTHLY_SMS_LIMIT_REACHED(60009),
24 | DAILY_SMS_LIMIT_REACHED(60010),
25 | MONTHLY_CALLS_LIMIT_REACHED(60011),
26 | DAILY_CALLS_LIMIT_REACHED(60012),
27 | BANNED_COUNTRY(60013),
28 | CALL_NOT_STARTED(60014),
29 | SMS_NOT_SENT(60015),
30 | USER_DOES_NOT_EXIST(60016),
31 | USER_SUSPENDED(60017),
32 | USER_DISABLED(60018),
33 | REUSED_TOKEN(60019),
34 | TOKEN_INVALID(60020),
35 | CANNOT_CREATE_PHONE_VERIFICATION(60021),
36 | PHONE_VERIFICATION_INCORRECT(60022),
37 | PHONE_VERIFICATION_NOT_FOUND(60023),
38 | CANNOT_GET_PHONE_INFO(60024),
39 | PHONE_INFO_ERROR_QUERYING(60025),
40 | USER_NOT_FOUND(60026),
41 | USER_NOT_VALID(60027),
42 | COULD_NOT_DELETE_USER(60028),
43 | CANNOT_CREATE_ACTIVITY(60029),
44 | USER_INCORRECT_PARAMS(60030),
45 | ACTION_NOT_AUTHORIZED(60031),
46 | SMS_NOT_FOUND(60032),
47 | INVALID_PHONE_NUMBER(60033),
48 | REGISTRATION_REQUEST_INVALID(60034),
49 | REGISTRATION_REQUEST_NOT_FOUND(60035),
50 | REGISTRATION_INVALID_PIN(60036),
51 | REGISTRATION_EXPIRED(60037),
52 | INVALID_EMAIL(60038),
53 | PHONE_VERIFICATION_PARAMS_INVALID(60042),
54 | TWILIO_API_KEY_DETECTED(60047),
55 | ONETOUCH_APPROVAL_REQUEST_NOT_FOUND(60049),
56 | ONETOUCH_UNREGISTERED_USER(60050),
57 | ONETOUCH_DEVICE_NOT_FOUND(60051),
58 | ONETOUCH_INTERNAL_CONNECTION_ERROR(60052),
59 | ONETOUCH_SENDING_APPROVAL_REQUEST_ERROR(60053),
60 | ONETOUCH_APPROVAL_REQUEST_ERROR(60054),
61 | ONETOUCH_NOTIFYING_CUSTOMER_ERROR(60055),
62 | MUST_USE_SSL(60056),
63 | ACCOUNT_SUSPENDED_TEMPORARILY(60057),
64 | PHONE_NUMBER_NOT_FOUND(60058),
65 | PHONE_NUMBER_INVALID(60059),
66 | TWILIO_ACCOUNT_SUSPENDED(60060),
67 | APPLICATION_SUSPENDED(60061),
68 | DISALLOWED_IP(60063),
69 | CANNOT_ENABLE_ONETOUCH(60064),
70 | ONETOUCH_CANNOT_SAVE_CALLBACK(60066),
71 | CANNOT_UPDATE_ON_DEVICE_REGISTRATION(60068),
72 | ACCESS_KEY_ERROR(60069),
73 | INVALID_APPLICATION(60070),
74 | ACCESS_KEY_NOT_FOUND(60071),
75 | INVALID_ACCESS_KEY(60072),
76 | INVALID_APPLICATION_API_KEY(60073),
77 | ACCESS_KEY_PERMISSION_DENIED(60074),
78 | CANNOT_DELETE_APPLICATION(60075),
79 | COUNTRY_CODE_VALIDATION_FAIL(60078),
80 | ONETOUCH_APPROVAL_REQUEST_NOT_PENDING(60079),
81 | ONETOUCH_APPROVAL_REQUEST_INVALID(60080),
82 | CANNOT_SEND_SMS_TO_LANDLINE(60082),
83 | PHONE_NUMBER_NOT_PROVISIONED(60083),
84 | JWT_TOKEN_EXPIRED(60086),
85 | INVALID_SIGNATURE(60087),
86 | INVALID_REPORTING_QUERY(60089),
87 | REGISTRATION_REQUEST_COULD_NOT_BE_CREATED(60090),
88 | CUSTOM_MESSAGE_DISALLOWED(60091),
89 | DEVICE_NOT_FOUND(60092),
90 | SDK_DEVICE_NOT_DELETED(60093),
91 | INVALID_REPORTING_INTERVAL(60094),
92 | INVALID_REPORTING_REPORT(60095),
93 | ERROR_PROCESSING_REPORT(60096),
94 | PHONE_CHANGE_IN_PROGRESS(60097),
95 | WEBHOOK_CREATION_ERROR(60098),
96 | WEBHOOK_LIST_ERROR(60099),
97 | WEBHOOK_DELETION_ERROR(60100),
98 | INVALID_JWT_TOKEN(60101),
99 | PUSH_CERT_CREATION_ERROR(60102),
100 | NOT_RECOGNIZED_PUSH_PLATFORM(60103),
101 | INVALID_PUSH_CERTS(60104),
102 | NOTIFY_JWT_TOKEN_ERROR(60105),
103 | USER_SUSPENDED_FROM_APP(60106),
104 | USER_BLOCKED(60107),
105 | INVALID_CHANNEL_FOR_DEVICE(60108),
106 | AUTHENTICATION_METHOD_NOT_FOUND(60109),
107 | AUTHENTICATION_METHOD_CANNOT_BE_CREATED(60110),
108 | AUTHENTICATION_NOT_FOUND(60111),
109 | INVALID_AUTHENTICATION_METHOD(60112),
110 | AUTHENTICATOR_NOT_FOUND(60113),
111 | AUTHENTICATOR_CANNOT_BE_UPDATED(60114),
112 | NUMBER_OPTED_OUT(60115),
113 | BAD_PV_JWT_PARAMS(60116),
114 | APPLICATION_NOT_FOUND(60117),
115 | TOTP_CODE_INVALID(60118),
116 | USER_WITHOUT_PII_REQUIRED(60119),
117 | SMS_LIMIT_REACHED(60120),
118 | SMS_INVALID(60121),
119 | QR_CODE_GENERATION_FAILED(60122),
120 | GENERIC_TOKENS_DISABLED(60123),
121 | ACCOUNT_NOT_FOUND(60124),
122 | INVALID_SDK_APP(60125),
123 | HLR_REPORT_ERROR(60126),
124 | USER_PENDING_FOR_DELETION(60127),
125 | USER_WAS_DELETED(60128),
126 | PUBLIC_KEY_NOT_FOUND(60129),
127 | USER_DELETION_ON_GOING(60130),
128 | USER_DELETION_FAILED(60131),
129 | ACCOUNT_DELETION_INCOMPLETE(60132),
130 | CLNPC_MESSAGE(60133) ;
131 |
132 | private final int number;
133 |
134 | Code(int number) {
135 | this.number = number;
136 | }
137 |
138 | public int getNumber() {
139 | return number;
140 | }
141 | }
142 |
143 | private String message, url, countryCode;
144 | private Code code;
145 |
146 | @XmlElement(name = "country-code")
147 | public String getCountryCode() {
148 | return countryCode;
149 | }
150 |
151 | public void setCountryCode(String countryCode) {
152 | this.countryCode = countryCode;
153 | }
154 |
155 | @XmlElement(name = "message")
156 | public String getMessage() {
157 | return message;
158 | }
159 |
160 | public void setMessage(String message) {
161 | this.message = message;
162 | }
163 |
164 | @XmlElement(name = "url")
165 | public String getUrl() {
166 | return url;
167 | }
168 |
169 | public Code getCode() {
170 | return code;
171 | }
172 |
173 | public void setCode(Code code) {
174 | this.code = code;
175 | }
176 |
177 | public void setUrl(String url) {
178 | this.url = url;
179 | }
180 |
181 | /**
182 | * Map a Token instance to its XML representation.
183 | *
184 | * @return a String with the description of this object in XML.
185 | */
186 | public String toXML() {
187 | StringWriter sw = new StringWriter();
188 | String xml = "";
189 |
190 | try {
191 | JAXBContext context = JAXBContext.newInstance(this.getClass());
192 | Marshaller marshaller = context.createMarshaller();
193 |
194 | marshaller.marshal(this, sw);
195 | xml = sw.toString();
196 | } catch (Exception e) {
197 | e.printStackTrace();
198 | }
199 | return xml;
200 | }
201 |
202 | /**
203 | * Map a Token instance to its Java's Map representation.
204 | *
205 | * @return a Java's Map with the description of this object.
206 | */
207 | public Map toMap() {
208 | Map map = new HashMap<>();
209 |
210 | map.put("message", message);
211 | map.put("country-code", countryCode);
212 | map.put("url", url);
213 |
214 | return map;
215 | }
216 |
217 | @Override
218 | public String toString() {
219 | return "Error [message=" + message + ", url=" + url + ", countryCode="
220 | + countryCode + "]";
221 | }
222 |
223 | }
224 |
--------------------------------------------------------------------------------
/src/main/java/com/authy/api/Formattable.java:
--------------------------------------------------------------------------------
1 | package com.authy.api;
2 |
3 | import org.json.JSONObject;
4 |
5 | import java.util.Map;
6 |
7 | /**
8 | * Interface to represent objects as XML or Java's Map
9 | *
10 | * @author Authy Inc
11 | */
12 | public interface Formattable {
13 | String toXML();
14 |
15 | Map toMap();
16 |
17 | default String toJSON() {
18 | JSONObject json = new JSONObject();
19 | for (Map.Entry entry : toMap().entrySet()) {
20 | json.put(entry.getKey(), entry.getValue());
21 | }
22 |
23 | return json.toString();
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/main/java/com/authy/api/Hash.java:
--------------------------------------------------------------------------------
1 | package com.authy.api;
2 |
3 | import javax.xml.bind.annotation.XmlElement;
4 | import javax.xml.bind.annotation.XmlRootElement;
5 | import java.util.HashMap;
6 | import java.util.Map;
7 |
8 | /**
9 | * @author Julian Camargo
10 | *
11 | * Copyright © 2017 Twilio, Inc. All Rights Reserved.
12 |
13 | */
14 | @XmlRootElement(name = "hash")
15 | public class Hash extends Instance implements Formattable {
16 |
17 | private User user = null;
18 | private String token;
19 | private boolean success;
20 |
21 | public Hash() {
22 | }
23 |
24 | public Hash(int status, String content) {
25 | super(status, content);
26 | }
27 |
28 | @XmlElement(type = User.class)
29 | public User getUser() {
30 | return user;
31 | }
32 |
33 | public void setUser(User user) {
34 | this.user = user;
35 | }
36 |
37 | public String getMessage() {
38 | return message;
39 | }
40 |
41 | public void setMessage(String message) {
42 | this.message = message;
43 | }
44 |
45 | public String getToken() {
46 | return token;
47 | }
48 |
49 | public void setToken(String token) {
50 | this.token = token;
51 | }
52 |
53 | public boolean isSuccess() {
54 | return success;
55 | }
56 |
57 | public void setSuccess(boolean success) {
58 | this.success = success;
59 | }
60 |
61 | /**
62 | * Map a Token instance to its Java's Map representation.
63 | *
64 | * @return a Java's Map with the description of this object.
65 | */
66 | public Map toMap() {
67 |
68 | HashMap map = new HashMap<>();
69 |
70 | if( user != null ) {
71 |
72 | Map userMap = user.toMap();
73 |
74 | for(String st : userMap.keySet() ){
75 | map.put("user."+st,userMap.get(st));
76 | }
77 |
78 | }
79 |
80 | map.put("message",message);
81 | map.put("token",token);
82 | map.put("success",String.valueOf(success));
83 | return map;
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/src/main/java/com/authy/api/Instance.java:
--------------------------------------------------------------------------------
1 | package com.authy.api;
2 |
3 | import java.io.StringWriter;
4 |
5 | import javax.xml.bind.JAXBContext;
6 | import javax.xml.bind.Marshaller;
7 |
8 | /**
9 | * Generic class to instance a response from the API
10 | *
11 | * @author Julian Camargo
12 | */
13 |
14 | public class Instance {
15 | int status;
16 | String content;
17 | String message;
18 | Error error;
19 |
20 | public Instance() {
21 | content = "";
22 | }
23 |
24 | public Instance(int status, String content) {
25 | this.status = status;
26 | this.content = content;
27 | }
28 |
29 | public Instance(int status, String content, String message) {
30 | this.status = status;
31 | this.content = content;
32 | this.message = message;
33 | }
34 |
35 | /**
36 | * Check if this is instance is correct. (i.e No error occurred)
37 | *
38 | * @return true if no error occurred else false.
39 | */
40 | public boolean isOk() {
41 | return status == 200;
42 | }
43 |
44 | /**
45 | * Return an Error object with the error that have occurred or null.
46 | *
47 | * @return an Error object
48 | */
49 | public Error getError() {
50 | return error;
51 | }
52 |
53 | /**
54 | * Set an Error object.
55 | */
56 | public void setError(Error error) {
57 | this.error = error;
58 | }
59 |
60 | public int getStatus() {
61 | return this.status;
62 | }
63 |
64 | public void setStatus(int status) {
65 | this.status = status;
66 | }
67 |
68 | /**
69 | * Map a Token instance to its XML representation.
70 | *
71 | * @return a String with the description of this object in XML.
72 | */
73 | public String toXML() {
74 | Error error = getError();
75 |
76 | if (error != null) {
77 | return error.toXML();
78 | }
79 |
80 | StringWriter sw = new StringWriter();
81 | String xml = "";
82 |
83 | try {
84 | JAXBContext context = JAXBContext.newInstance(this.getClass());
85 | Marshaller marshaller = context.createMarshaller();
86 |
87 | marshaller.marshal(this, sw);
88 | xml = sw.toString();
89 | } catch (Exception e) {
90 | e.printStackTrace();
91 | }
92 | return xml;
93 | }
94 | }
95 |
96 |
--------------------------------------------------------------------------------
/src/main/java/com/authy/api/JSONBody.java:
--------------------------------------------------------------------------------
1 | package com.authy.api;
2 |
3 | import com.authy.AuthyUtil;
4 | import org.json.JSONObject;
5 |
6 | import java.util.HashMap;
7 | import java.util.Map;
8 |
9 | /**
10 | * @author hansospina
11 | *
12 | * Copyright © 2017 Twilio, Inc. All Rights Reserved.
13 | */
14 | public class JSONBody implements Formattable {
15 |
16 | private JSONObject obj;
17 |
18 | public JSONBody(JSONObject obj) {
19 | this.obj = obj != null ? obj : new JSONObject();
20 | }
21 |
22 | public String toXML() {
23 | return null;
24 | }
25 |
26 | public Map toMap() {
27 |
28 | HashMap map = new HashMap<>();
29 | AuthyUtil.extract("", obj, map);
30 |
31 | return map;
32 | }
33 |
34 | public String toJSON() {
35 | return obj.toString();
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/main/java/com/authy/api/Logo.java:
--------------------------------------------------------------------------------
1 | /*
2 | * To change this license header, choose License Headers in Project Properties.
3 | * To change this template file, choose Tools | Templates
4 | * and open the template in the editor.
5 | */
6 | package com.authy.api;
7 |
8 | import org.json.JSONArray;
9 | import org.json.JSONObject;
10 |
11 | /**
12 | * @author hansospina
13 | *
14 | * Copyright © 2017 Twilio, Inc. All Rights Reserved.
15 | */
16 | public class Logo {
17 |
18 |
19 | private final int MAX = 201;
20 | private String res;
21 | private String url;
22 |
23 |
24 | public Logo(ApprovalRequestParams.Resolution res, String url) {
25 | this.res = res.getRes();
26 | if (url == null)
27 | this.url = "";
28 | else
29 | this.url = url.substring(0, Math.min(url.length(), MAX));
30 | }
31 |
32 | public String getRes() {
33 | return res;
34 | }
35 |
36 | public void setRes(ApprovalRequestParams.Resolution res) {
37 | this.res = res.getRes();
38 | }
39 |
40 | public String getUrl() {
41 | return url;
42 | }
43 |
44 | public void setUrl(String url) {
45 | if (url == null)
46 | this.url = "";
47 | else
48 | this.url = url.substring(0, Math.min(url.length(), MAX));
49 | }
50 |
51 | public void addToMap(JSONArray map) {
52 | if (!getUrl().isEmpty()) {
53 | JSONObject temp = new JSONObject();
54 | temp.put("res", getRes());
55 | temp.put("url", getUrl());
56 | map.put(temp);
57 | }
58 | }
59 |
60 |
61 | }
62 |
--------------------------------------------------------------------------------
/src/main/java/com/authy/api/OneTouch.java:
--------------------------------------------------------------------------------
1 | package com.authy.api;
2 |
3 | import com.authy.AuthyException;
4 | import com.authy.OneTouchException;
5 |
6 | import org.json.JSONArray;
7 | import org.json.JSONObject;
8 |
9 | import java.net.URLEncoder;
10 | import java.util.Map;
11 |
12 | /**
13 | * @author hansospina
14 | *
15 | * Copyright © 2017 Twilio, Inc. All Rights Reserved.
16 | */
17 | public class OneTouch extends Resource {
18 |
19 | public static final String APPROVAL_REQUEST_PRE = "/onetouch/json/users/";
20 | public static final String APPROVAL_REQUEST_POS = "/approval_requests";
21 | public static final String APPROVAL_REQUEST_STATUS = "/onetouch/json/approval_requests/";
22 |
23 |
24 | public OneTouch(String uri, String key) {
25 | super(uri, key, Resource.JSON_CONTENT_TYPE);
26 |
27 | }
28 |
29 | public OneTouch(String uri, String apiKey, boolean testFlag) {
30 | super(uri, apiKey, testFlag, Resource.JSON_CONTENT_TYPE);
31 | }
32 |
33 |
34 | /**
35 | * Sends the OneTouch's approval request to the Authy servers and returns the OneTouchResponse that comes back.
36 | *
37 | * @param approvalRequestParams The bean wrapping the user's Authy approval request built using the ApprovalRequest.Builder
38 | * @return The bean wrapping the response from Authy's service.
39 | */
40 | public OneTouchResponse sendApprovalRequest(ApprovalRequestParams approvalRequestParams)
41 | throws AuthyException {//Integer userId, String message, HashMap options, Integer secondsToExpire) throws OneTouchException {
42 |
43 |
44 | JSONObject params = new JSONObject();
45 | params.put("message", approvalRequestParams.getMessage());
46 |
47 |
48 | if (approvalRequestParams.getSecondsToExpire() != null) {
49 | params.put("seconds_to_expire", approvalRequestParams.getSecondsToExpire());
50 | }
51 |
52 | if (approvalRequestParams.getDetails().size() > 0) {
53 | params.put("details", mapToJSONObject(approvalRequestParams.getDetails()));
54 | }
55 |
56 |
57 | if (approvalRequestParams.getHidden().size() > 0) {
58 | params.put("hidden_details", mapToJSONObject(approvalRequestParams.getHidden()));
59 | }
60 |
61 |
62 | if (!approvalRequestParams.getLogos().isEmpty()) {
63 | JSONArray jSONArray = new JSONArray();
64 |
65 | for (Logo logo : approvalRequestParams.getLogos()) {
66 | logo.addToMap(jSONArray);
67 | }
68 |
69 | params.put("logos", jSONArray);
70 | }
71 |
72 | final Response response = this.post(APPROVAL_REQUEST_PRE + approvalRequestParams.getAuthyId() + APPROVAL_REQUEST_POS, new JSONBody(params));
73 | OneTouchResponse oneTouchResponse = new OneTouchResponse(response.getStatus(), response.getBody());
74 |
75 | if (!oneTouchResponse.isOk()) {
76 | oneTouchResponse.setError(errorFromJson(response.getBody()));
77 | }
78 | return oneTouchResponse;
79 | }
80 |
81 | public OneTouchResponse getApprovalRequestStatus(String uuid) throws OneTouchException {
82 |
83 | try {
84 | final Response response = this.get(APPROVAL_REQUEST_STATUS + URLEncoder.encode(uuid, ENCODE), new Params());
85 | OneTouchResponse oneTouchResponse = new OneTouchResponse(response.getStatus(), response.getBody());
86 | if (!oneTouchResponse.isOk()) {
87 | oneTouchResponse.setError(errorFromJson(response.getBody()));
88 | }
89 | return oneTouchResponse;
90 |
91 | } catch (Exception e) {
92 | throw new OneTouchException("There was an error trying to process this request.", e);
93 | }
94 |
95 | }
96 |
97 |
98 | private JSONObject mapToJSONObject(Map map) {
99 |
100 | JSONObject obj = new JSONObject();
101 |
102 | for (String key : map.keySet()) {
103 | obj.put(key, map.get(key));
104 | }
105 |
106 | return obj;
107 | }
108 |
109 |
110 | }
111 |
--------------------------------------------------------------------------------
/src/main/java/com/authy/api/OneTouchResponse.java:
--------------------------------------------------------------------------------
1 | package com.authy.api;
2 |
3 | import com.authy.AuthyException;
4 |
5 | import org.json.JSONException;
6 | import org.json.JSONObject;
7 |
8 | /**
9 | * @author hansospina
10 | *
11 | * Copyright © 2017 Twilio, Inc. All Rights Reserved.
12 | */
13 | public class OneTouchResponse extends Instance {
14 |
15 | private JSONObject obj;
16 |
17 |
18 | public OneTouchResponse(int status, String content) throws AuthyException {
19 | super(status, content);
20 | try {
21 | obj = new JSONObject(content);
22 | } catch (JSONException ex) {
23 | throw new AuthyException("Invalid JSON format, the given string is not a valid json object.", ex);
24 | }
25 | }
26 |
27 | public OneTouchResponse(String json) throws AuthyException {
28 | this(200, json);
29 | }
30 |
31 | public boolean isSuccess() {
32 | return obj.has("success") && obj.getBoolean("success");
33 | }
34 |
35 | public String getMessage() {
36 | return obj.has("message") ? obj.getString("message") : "";
37 | }
38 |
39 | public String getErrorCode() {
40 | return obj.has("error_code") ? obj.getString("error_code") : "";
41 | }
42 |
43 |
44 | public ApprovalRequest getApprovalRequest() {
45 |
46 | if (obj.has("approval_request")) {
47 | return new ApprovalRequest();
48 | }
49 |
50 | return null;
51 | }
52 |
53 | public class ApprovalRequest {
54 |
55 | private ApprovalRequest() {
56 | }
57 |
58 | public boolean isNotified() {
59 | return obj.getJSONObject("approval_request").has("notified") && obj.getJSONObject("approval_request").getBoolean("notified");
60 | }
61 |
62 | public String createdAt() {
63 | return obj.getJSONObject("approval_request").has("created_at") ? obj.getJSONObject("approval_request").getString("created_at") : null;
64 | }
65 |
66 | public String getUUID() {
67 | return obj.getJSONObject("approval_request").has("uuid") ? obj.getJSONObject("approval_request").getString("uuid") : null;
68 | }
69 |
70 | public String getStatus() {
71 | return obj.getJSONObject("approval_request").has("status") ? obj.getJSONObject("approval_request").getString("status") : null;
72 | }
73 |
74 | // if the user was a value that is not mapped previously
75 | public String getValue(String key) {
76 | return obj.getJSONObject("approval_request").has(key) ? obj.getJSONObject("approval_request").getString(key) : null;
77 | }
78 | }
79 |
80 |
81 | }
82 |
--------------------------------------------------------------------------------
/src/main/java/com/authy/api/Params.java:
--------------------------------------------------------------------------------
1 | package com.authy.api;
2 |
3 | import java.util.HashMap;
4 | import java.util.Map;
5 |
6 | /**
7 | * @author Authy Inc
8 | */
9 |
10 | public class Params implements Formattable {
11 | private Map data;
12 |
13 | public Params() {
14 | data = new HashMap<>();
15 | }
16 |
17 | public void setAttribute(String key, String value) {
18 | this.data.put(key, value);
19 | }
20 |
21 | // required to satisfy Formattable interface
22 | public String toXML() {
23 | return "";
24 | }
25 |
26 | public Map toMap() {
27 | return this.data;
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/main/java/com/authy/api/PhoneInfo.java:
--------------------------------------------------------------------------------
1 | package com.authy.api;
2 |
3 | import com.authy.AuthyException;
4 |
5 | /**
6 | * @author Moisés Vargas
7 | */
8 | public class PhoneInfo extends Resource {
9 | public static final String PHONE_INFO_API_PATH = "/protected/json/phones/";
10 |
11 | public PhoneInfo(String uri, String key) {
12 | super(uri, key, Resource.JSON_CONTENT_TYPE);
13 | }
14 |
15 | public PhoneInfo(String uri, String key, boolean testFlag) {
16 | super(uri, key, testFlag, Resource.JSON_CONTENT_TYPE);
17 | }
18 |
19 | public PhoneInfoResponse info(String phoneNumber, String countryCode) throws AuthyException {
20 | return info(phoneNumber, countryCode, new Params());
21 | }
22 |
23 | public PhoneInfoResponse info(String phoneNumber, String countryCode, Params params) throws AuthyException {
24 | params.setAttribute("phone_number", phoneNumber);
25 | params.setAttribute("country_code", countryCode);
26 | final Response response = this.get(PHONE_INFO_API_PATH + "info", params);
27 | PhoneInfoResponse info = new PhoneInfoResponse(response.getStatus(), response.getBody());
28 | if (!info.isOk()) {
29 | info.setError(errorFromJson(response.getBody()));
30 | }
31 | return info;
32 | }
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/src/main/java/com/authy/api/PhoneInfoResponse.java:
--------------------------------------------------------------------------------
1 | package com.authy.api;
2 |
3 | import org.json.JSONObject;
4 |
5 | import java.util.HashMap;
6 | import java.util.Map;
7 |
8 | /**
9 | * @author Moisés Vargas
10 | */
11 |
12 | public class PhoneInfoResponse extends Instance implements Formattable {
13 | private String message = "Something went wrong!";
14 | private String provider = "";
15 | private String type = "";
16 | private boolean isPorted = false;
17 |
18 | public PhoneInfoResponse() {
19 | }
20 |
21 | public PhoneInfoResponse(int status, String response) {
22 | this(status, response, null);
23 | }
24 |
25 | public PhoneInfoResponse(int status, String response, String message) {
26 | this.status = status;
27 | this.content = response;
28 | this.message = message;
29 | this.setResponse(response);
30 | }
31 |
32 | public String getMessage() {
33 | return message;
34 | }
35 |
36 | public String getProvider() {
37 | return provider;
38 | }
39 |
40 | public String getType() {
41 | return type;
42 | }
43 |
44 | public String getSuccess() {
45 | return Boolean.toString(this.isOk());
46 | }
47 |
48 | public String getIsPorted() {
49 | return Boolean.toString(this.isPorted);
50 | }
51 |
52 | public void setResponse(String response) {
53 | this.content = response;
54 | JSONObject jsonResponse = new JSONObject(response);
55 | this.parseResponseToObject(jsonResponse);
56 | }
57 |
58 | /**
59 | * Map a Token instance to its Java's Map representation.
60 | *
61 | * @return a Java's Map with the description of this object.
62 | */
63 | public Map toMap() {
64 | Map map = new HashMap<>();
65 |
66 | map.put("message", this.getMessage());
67 | map.put("success", this.getSuccess());
68 | map.put("is_ported", this.getIsPorted());
69 | map.put("provider", this.getProvider());
70 | map.put("type", this.getType());
71 |
72 |
73 | return map;
74 | }
75 |
76 | private void parseResponseToObject(JSONObject json) {
77 | if (!json.isNull("message")) {
78 | this.message = json.getString("message");
79 | }
80 |
81 | if (!json.isNull("ported")) {
82 | this.isPorted = json.getBoolean("ported");
83 | }
84 |
85 | if (!json.isNull("provider")) {
86 | this.provider = json.getString("provider");
87 | }
88 |
89 | if (!json.isNull("type")) {
90 | this.type = json.getString("type");
91 | }
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/src/main/java/com/authy/api/PhoneVerification.java:
--------------------------------------------------------------------------------
1 | package com.authy.api;
2 |
3 | import com.authy.AuthyException;
4 |
5 | /**
6 | * @author Authy Inc
7 | */
8 | public class PhoneVerification extends Resource {
9 | public static final String PHONE_VERIFICATION_API_PATH = "/protected/json/phones/verification/";
10 |
11 | public PhoneVerification(String uri, String key) {
12 | super(uri, key, Resource.JSON_CONTENT_TYPE);
13 | }
14 |
15 | public PhoneVerification(String uri, String key, boolean testFlag) {
16 | super(uri, key, testFlag, Resource.JSON_CONTENT_TYPE);
17 | }
18 |
19 | public Verification start(String phoneNumber, String countryCode, String via, Params params) throws AuthyException {
20 | params.setAttribute("phone_number", phoneNumber);
21 | params.setAttribute("country_code", countryCode);
22 | params.setAttribute("via", via);
23 |
24 | final Response response = this.post(PHONE_VERIFICATION_API_PATH + "start", params);
25 |
26 | Verification verification = new Verification(response.getStatus(), response.getBody());
27 | if (!verification.isOk())
28 | verification.setError(errorFromJson(response.getBody()));
29 |
30 | return verification;
31 | }
32 |
33 | public Verification check(String phoneNumber, String countryCode, String code) throws AuthyException {
34 | return check(phoneNumber,countryCode, code, new Params());
35 | }
36 |
37 | public Verification check(String phoneNumber, String countryCode, String code, Params params) throws AuthyException {
38 | params.setAttribute("phone_number", phoneNumber);
39 | params.setAttribute("country_code", countryCode);
40 | params.setAttribute("verification_code", code);
41 |
42 | final Response response = this.get(PHONE_VERIFICATION_API_PATH + "check", params);
43 |
44 | Verification verification = new Verification(response.getStatus(), response.getBody());
45 | if (!verification.isOk())
46 | verification.setError(errorFromJson(response.getBody()));
47 | return verification;
48 |
49 | }
50 |
51 | }
52 |
--------------------------------------------------------------------------------
/src/main/java/com/authy/api/Resource.java:
--------------------------------------------------------------------------------
1 | package com.authy.api;
2 |
3 | import com.authy.AuthyApiClient;
4 | import com.authy.AuthyException;
5 | import org.json.JSONException;
6 | import org.json.JSONObject;
7 |
8 | import javax.net.ssl.HttpsURLConnection;
9 | import javax.net.ssl.SSLHandshakeException;
10 | import java.io.*;
11 | import java.net.HttpURLConnection;
12 | import java.net.MalformedURLException;
13 | import java.net.URL;
14 | import java.net.URLEncoder;
15 | import java.util.Arrays;
16 | import java.util.HashMap;
17 | import java.util.Map;
18 | import java.util.Map.Entry;
19 | import java.util.Objects;
20 | import java.util.logging.Level;
21 | import java.util.logging.Logger;
22 |
23 | /**
24 | * Class to send http requests.
25 | *
26 | * @author Julian Camargo
27 | */
28 | public class Resource {
29 | public static class Response {
30 | private final Integer status;
31 | private final String body;
32 |
33 | Response(Integer status, String body) {
34 | this.status = status;
35 | this.body = body;
36 | }
37 |
38 | public Integer getStatus() {
39 | return status;
40 | }
41 |
42 | public String getBody() {
43 | return body;
44 | }
45 |
46 | public String toString() {
47 | return "Response[" + status + ", " + body + "]";
48 | }
49 |
50 | public boolean equals(Object o) {
51 | return this == o || o instanceof Response && Objects.equals(status, ((Response) o).status) && Objects.equals(body, ((Response) o).body);
52 | }
53 |
54 | public int hashCode() {
55 | return Objects.hash(status, body);
56 | }
57 | }
58 |
59 | private static final Logger LOGGER = Logger.getLogger(Resource.class.getName());
60 |
61 | public static final String METHOD_POST = "POST";
62 | public static final String METHOD_GET = "GET";
63 | public static final String METHOD_PUT = "PUT";
64 | public static final String ENCODE = "UTF-8";
65 | public static final String XML_CONTENT_TYPE = "application/xml";
66 | public static final String JSON_CONTENT_TYPE = "application/json";
67 |
68 | private final String apiUri;
69 | private final String apiKey;
70 | private final boolean testFlag;
71 | private Map defaultOptions;
72 | private final boolean isJSON;
73 | private final String contentType;
74 |
75 | public Resource(String uri, String key) {
76 | this(uri, key, false, JSON_CONTENT_TYPE);
77 | }
78 |
79 | public Resource(String uri, String key, String contentType) {
80 | this(uri, key, false, contentType);
81 | }
82 |
83 | public Resource(String uri, String key, boolean testFlag) {
84 | this(uri, key, testFlag, JSON_CONTENT_TYPE);
85 | }
86 |
87 | public Resource(String uri, String key, boolean testFlag, String contentType) {
88 | apiUri = uri;
89 | apiKey = key;
90 | this.testFlag = testFlag;
91 | this.contentType = (contentType == null || contentType.equals(XML_CONTENT_TYPE) || !contentType.equals(JSON_CONTENT_TYPE)) ? XML_CONTENT_TYPE : JSON_CONTENT_TYPE;
92 | isJSON = this.contentType.equals(JSON_CONTENT_TYPE);
93 | }
94 |
95 | /**
96 | * POST method.
97 | *
98 | * @param path
99 | * @param data
100 | * @return response from API.
101 | */
102 | public Response post(String path, Formattable data) throws AuthyException {
103 | return request(Resource.METHOD_POST, path, data, getDefaultOptions());
104 | }
105 |
106 | /**
107 | * GET method.
108 | *
109 | * @param path
110 | * @param data
111 | * @return response from API.
112 | */
113 | public Response get(String path, Formattable data) throws AuthyException {
114 | return request(Resource.METHOD_GET, path, data, getDefaultOptions());
115 | }
116 |
117 | /**
118 | * PUT method.
119 | *
120 | * @param path
121 | * @param data
122 | * @return response from API.
123 | */
124 | public Response put(String path, Formattable data) throws AuthyException {
125 | return request(Resource.METHOD_PUT, path, data, getDefaultOptions());
126 | }
127 |
128 | /**
129 | * DELETE method.
130 | *
131 | * @param path
132 | * @param data
133 | * @return response from API.
134 | */
135 | public Response delete(String path, Formattable data) throws AuthyException {
136 | return request("DELETE", path, data, getDefaultOptions());
137 | }
138 |
139 | private Response request(String method, String path, Formattable data, Map options) throws AuthyException {
140 | HttpURLConnection connection;
141 |
142 | try {
143 | StringBuilder sb = new StringBuilder();
144 |
145 | if (method.equals(Resource.METHOD_GET)) {
146 | sb.append(prepareGet(data));
147 | }
148 |
149 | URL url = new URL(apiUri + path + sb.toString());
150 | connection = createConnection(url, method, options);
151 |
152 | connection.setRequestProperty("X-Authy-API-Key", apiKey);
153 |
154 | // data might be sent as a null value for cases like "DELETE" requests
155 | if (data!= null && data.toMap().containsKey("api_key")) {
156 | LOGGER.log(Level.WARNING, "Found 'api_key' as a parameter, please remove it, Authy-Java already handles the'api_key' for you.");
157 | }
158 | if (method.equals(Resource.METHOD_POST) || method.equals(Resource.METHOD_PUT)) {
159 | if (isJSON) {
160 | writeJson(connection, data);
161 | } else {
162 | writeXml(connection, data);
163 | }
164 | }
165 |
166 | final int status = connection.getResponseCode();
167 | return new Response(status, getResponse(connection, status));
168 | } catch (SSLHandshakeException e) {
169 | throw new AuthyException("SSL verification is failing. Contact support@authy.com", e);
170 | } catch (MalformedURLException e) {
171 | throw new AuthyException("Invalid host", e);
172 | } catch (IOException e) {
173 | throw new AuthyException("Connection error", e);
174 | }
175 | }
176 |
177 | Error errorFromJson(String content) throws AuthyException {
178 | try {
179 | JSONObject errorJson = new JSONObject(content);
180 | Error error = new Error();
181 | error.setMessage(errorJson.getString("message"));
182 | final int errorCodeNumber = Integer.parseInt(errorJson.getString("error_code"));
183 | final Error.Code error_code = Arrays.stream(Error.Code.values())
184 | .filter(code -> code.getNumber() == errorCodeNumber)
185 | .findFirst()
186 | .orElse(Error.Code.DEFAULT_ERROR);
187 | error.setCode(error_code);
188 | return error;
189 | } catch (JSONException| NumberFormatException e) {
190 | throw new AuthyException("Invalid response from server", e);
191 | }
192 | }
193 |
194 | public String getContentType() {
195 | return this.contentType;
196 | }
197 |
198 | private HttpURLConnection createConnection(URL url, String method,
199 | Map options) throws IOException {
200 |
201 |
202 | HttpURLConnection connection;
203 | if (testFlag)
204 | connection = (HttpURLConnection) url.openConnection();
205 | else
206 | connection = (HttpsURLConnection) url.openConnection();
207 |
208 | connection.setRequestMethod(method);
209 |
210 | for (Entry s : options.entrySet()) {
211 | connection.setRequestProperty(s.getKey(), s.getValue());
212 | }
213 |
214 | connection.setDoOutput(true);
215 |
216 | return connection;
217 | }
218 |
219 | private String getResponse(HttpURLConnection connection, int status) throws IOException {
220 | InputStream in;
221 | // Load stream
222 | if (status != 200) {
223 | in = connection.getErrorStream();
224 | } else {
225 | in = connection.getInputStream();
226 | }
227 |
228 | BufferedInputStream input = new BufferedInputStream(in);
229 | StringBuilder sb = new StringBuilder();
230 | int ch;
231 |
232 | while ((ch = input.read()) != -1) {
233 | sb.append((char) ch);
234 | }
235 | input.close();
236 |
237 | return sb.toString();
238 | }
239 |
240 | private void writeXml(HttpURLConnection connection, Formattable data) throws IOException {
241 | if (data == null)
242 | return;
243 |
244 | OutputStream os = connection.getOutputStream();
245 |
246 | BufferedWriter output = new BufferedWriter(new OutputStreamWriter(os));
247 | output.write(data.toXML());
248 | output.flush();
249 | output.close();
250 | }
251 |
252 | private void writeJson(HttpURLConnection connection, Formattable data) throws IOException {
253 | if (data == null)
254 | return;
255 |
256 | OutputStream os = connection.getOutputStream();
257 | BufferedWriter output = new BufferedWriter(new OutputStreamWriter(os));
258 | output.write(data.toJSON());
259 | output.flush();
260 | output.close();
261 | }
262 |
263 |
264 | private String prepareGet(Formattable data) {
265 |
266 | if (data == null)
267 | return "";
268 |
269 | StringBuilder sb = new StringBuilder("?");
270 | Map params = data.toMap();
271 |
272 | boolean first = true;
273 |
274 | for (Entry s : params.entrySet()) {
275 |
276 | if (first) {
277 | first = false;
278 | } else {
279 | sb.append('&');
280 | }
281 |
282 | try {
283 | sb.append(URLEncoder.encode(s.getKey(), ENCODE)).append("=").append(URLEncoder.encode(s.getValue(), ENCODE));
284 | } catch (UnsupportedEncodingException e) {
285 | System.out.println("Encoding not supported" + e.getMessage());
286 | }
287 | }
288 |
289 |
290 | return sb.toString();
291 | }
292 |
293 | private Map getDefaultOptions() {
294 | if (this.defaultOptions == null || this.defaultOptions.isEmpty()) {
295 | this.defaultOptions = new HashMap<>();
296 | this.defaultOptions.put("Content-Type", contentType);
297 | this.defaultOptions.put("User-Agent", getUserAgent());
298 | }
299 | return this.defaultOptions;
300 | }
301 |
302 | private String getUserAgent() {
303 | String os = String.format("%s-%s-%s; Java %s", System.getProperty("os.name"), System.getProperty("os.version"),
304 | System.getProperty("os.arch"), System.getProperty("java.specification.version"));
305 | return String.format("%s/%s (%s)", AuthyApiClient.CLIENT_NAME, AuthyApiClient.VERSION, os);
306 | }
307 | }
308 |
--------------------------------------------------------------------------------
/src/main/java/com/authy/api/Token.java:
--------------------------------------------------------------------------------
1 | package com.authy.api;
2 |
3 | import javax.xml.bind.annotation.XmlRootElement;
4 | import java.util.HashMap;
5 | import java.util.Map;
6 |
7 | /**
8 | * @author Julian Camargo
9 | */
10 | @XmlRootElement(name = "token")
11 | public class Token extends Instance implements Formattable {
12 |
13 | public static final String VALID_TOKEN_MESSAGE = "Token is valid.";
14 |
15 | public Token() {
16 | }
17 |
18 | public Token(int status, String content){
19 | super(status, content);
20 | }
21 |
22 | public Token(int status, String content, String message) {
23 | super(status, content, message);
24 | }
25 |
26 | /**
27 | * Check if this is token is correct. (i.e No error occurred)
28 | *
29 | * @return true if no error occurred else false.
30 | */
31 | public boolean isOk() {
32 | if (super.isOk()) {
33 | return this.message.equals(VALID_TOKEN_MESSAGE);
34 | }
35 | return false;
36 | }
37 |
38 | /**
39 | * Map a Token instance to its Java's Map representation.
40 | *
41 | * @return a Java's Map with the description of this object.
42 | */
43 | public Map toMap() {
44 | Map map = new HashMap<>();
45 |
46 | map.put("status", Integer.toString(status));
47 | map.put("content", content);
48 |
49 | return map;
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/main/java/com/authy/api/Tokens.java:
--------------------------------------------------------------------------------
1 | package com.authy.api;
2 |
3 | import com.authy.AuthyException;
4 | import org.json.JSONException;
5 | import org.json.JSONObject;
6 | import sun.net.www.protocol.http.HttpURLConnection;
7 |
8 | import java.util.HashMap;
9 | import java.util.Map;
10 |
11 | /**
12 | * @author Julian Camargo
13 | */
14 | public class Tokens extends Resource {
15 | public static final String TOKEN_VERIFICATION_PATH = "/protected/json/verify/";
16 |
17 | public Tokens(String uri, String key) {
18 | super(uri, key);
19 | }
20 |
21 | public Tokens(String uri, String key, boolean testFlag) {
22 | super(uri, key, testFlag);
23 | }
24 |
25 | public Token verify(int userId, String token) throws AuthyException {
26 | return verify(userId, token, null);
27 | }
28 |
29 | public Token verify(int userId, String token, Map options) throws AuthyException {
30 | InternalToken internalToken = new InternalToken();
31 | internalToken.setOption(options);
32 |
33 | StringBuilder path = new StringBuilder(TOKEN_VERIFICATION_PATH);
34 | validateToken(token);
35 | path.append(token).append('/');
36 | path.append(userId);
37 |
38 | final Response response = this.get(path.toString(), internalToken);
39 | return tokenFromJson(response.getStatus(), response.getBody());
40 | }
41 |
42 | private Token tokenFromJson(int status, String content) throws AuthyException {
43 | if (status == 200) {
44 | try {
45 | JSONObject tokenJSON = new JSONObject(content);
46 | String message = tokenJSON.optString("message");
47 | return new Token(status, content, message);
48 |
49 | } catch (JSONException e) {
50 | throw new AuthyException("Invalid response from server", e, status);
51 | }
52 | }
53 |
54 | final Error error = errorFromJson(content);
55 | throw new AuthyException("Invalid token", status, error.getCode());
56 | }
57 |
58 | private void validateToken(String token) throws AuthyException {
59 | int len = token.length();
60 | if (!isInteger(token)) {
61 | throw new AuthyException("Invalid Token. Only digits accepted.", HttpURLConnection.HTTP_BAD_REQUEST,
62 | Error.Code.TOKEN_INVALID);
63 | }
64 | if (len < 6 || len > 10) {
65 | throw new AuthyException("Invalid Token. Unexpected length.", HttpURLConnection.HTTP_BAD_REQUEST,
66 | Error.Code.TOKEN_INVALID);
67 | }
68 | }
69 |
70 | private boolean isInteger(String s) {
71 | try {
72 | Long.parseLong(s);
73 | } catch (NumberFormatException e) {
74 | return false;
75 | }
76 | return true;
77 | }
78 |
79 | class InternalToken implements Formattable {
80 | Map options;
81 |
82 | InternalToken() {
83 | options = new HashMap<>();
84 | }
85 |
86 | void setOption(Map options) {
87 | if (options != null) {
88 | this.options = options;
89 | }
90 | }
91 |
92 | public String toXML() {
93 | return null;
94 | }
95 |
96 | public Map toMap() {
97 | if (!options.containsKey("force")) {
98 | options.put("force", "true");
99 | }
100 |
101 | return options;
102 | }
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/src/main/java/com/authy/api/User.java:
--------------------------------------------------------------------------------
1 | package com.authy.api;
2 |
3 | import javax.xml.bind.annotation.XmlElement;
4 | import javax.xml.bind.annotation.XmlRootElement;
5 | import java.util.HashMap;
6 | import java.util.Map;
7 |
8 | /**
9 | * @author Julian Camargo
10 | */
11 | @XmlRootElement(name = "user")
12 | public class User extends Instance implements Formattable {
13 | int id;
14 |
15 | public User() {
16 | }
17 |
18 | public User(int status, String content) {
19 | super(status, content);
20 | }
21 |
22 | public User(int status, String content, String message) {
23 | super(status, content, message);
24 | }
25 |
26 | @XmlElement(name = "id")
27 | public int getId() {
28 | return id;
29 | }
30 |
31 | public void setId(int id) {
32 | this.id = id;
33 | }
34 |
35 | /**
36 | * Map a Token instance to its Java's Map representation.
37 | *
38 | * @return a Java's Map with the description of this object.
39 | */
40 | public Map toMap() {
41 | Map map = new HashMap<>();
42 |
43 | map.put("id", Integer.toString(id));
44 | map.put("status", Integer.toString(status));
45 | map.put("content", content);
46 |
47 | return map;
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/main/java/com/authy/api/UserStatus.java:
--------------------------------------------------------------------------------
1 | package com.authy.api;
2 |
3 | import javax.xml.bind.annotation.XmlElement;
4 | import javax.xml.bind.annotation.XmlElementWrapper;
5 | import javax.xml.bind.annotation.XmlRootElement;
6 | import java.util.ArrayList;
7 | import java.util.HashMap;
8 | import java.util.List;
9 | import java.util.Map;
10 |
11 | @XmlRootElement(name = "user_status")
12 | public class UserStatus extends Instance implements Formattable {
13 |
14 | @XmlElement(name = "userId")
15 | private int userId;
16 | @XmlElement(name = "success")
17 | private boolean success;
18 | @XmlElement(name = "confirmed")
19 | private boolean confirmed;
20 | @XmlElement(name = "registered")
21 | private boolean registered;
22 | @XmlElement(name = "country_code")
23 | private int countryCode;
24 | @XmlElementWrapper
25 | @XmlElement(name = "device")
26 | private List devices;
27 | @XmlElement(name = "phone_number")
28 | private String phoneNumber;
29 |
30 | public UserStatus() {
31 | super();
32 | }
33 |
34 | public UserStatus(int status, String content) {
35 | super(status, content);
36 | }
37 |
38 | public UserStatus(int status, String content, String message) {
39 | super(status, content, message);
40 | }
41 |
42 | @Override
43 | public Map toMap() {
44 | Map map = new HashMap<>();
45 |
46 | map.put("userId", Integer.toString(getUserId()));
47 | map.put("success", Boolean.toString(getSuccess()));
48 | map.put("confirmed", Boolean.toString(isConfirmed()));
49 | map.put("registered", Boolean.toString(isRegistered()));
50 | map.put("countryCode", Integer.toString(getCountryCode()));
51 | map.put("phoneNumber", getPhoneNumber());
52 | map.put("devices", getDevices().toString());
53 |
54 | return map;
55 | }
56 |
57 | public int getUserId() {
58 | return userId;
59 | }
60 |
61 | void setUserId(int userId) {
62 | this.userId = userId;
63 | }
64 |
65 | void setMessage(String message) {
66 | this.message = message;
67 | }
68 |
69 | public String getMessage() {
70 | return message;
71 | }
72 |
73 | void setSuccess(boolean success) {
74 | this.success = success;
75 | }
76 |
77 | public boolean getSuccess() {
78 | return success;
79 | }
80 |
81 | public boolean isSuccess() {
82 | return success;
83 | }
84 |
85 | public boolean isConfirmed() {
86 | return confirmed;
87 | }
88 |
89 | void setConfirmed(boolean confirmed) {
90 | this.confirmed = confirmed;
91 | }
92 |
93 | public boolean isRegistered() {
94 | return registered;
95 | }
96 |
97 | void setRegistered(boolean registered) {
98 | this.registered = registered;
99 | }
100 |
101 | public int getCountryCode() {
102 | return countryCode;
103 | }
104 |
105 | void setCountryCode(int countryCode) {
106 | this.countryCode = countryCode;
107 | }
108 |
109 | public List getDevices() {
110 | if (devices == null) {
111 | devices = new ArrayList<>();
112 | }
113 | return devices;
114 | }
115 |
116 | void setDevices(List devices) {
117 | this.devices = devices;
118 | }
119 |
120 | void setPhoneNumber(String phoneNumber) {
121 | this.phoneNumber = phoneNumber;
122 | }
123 |
124 | public String getPhoneNumber() {
125 | return phoneNumber;
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/src/main/java/com/authy/api/Users.java:
--------------------------------------------------------------------------------
1 | package com.authy.api;
2 |
3 | import com.authy.AuthyException;
4 |
5 | import org.json.JSONArray;
6 | import org.json.JSONException;
7 | import org.json.JSONObject;
8 |
9 | import javax.xml.bind.JAXBContext;
10 | import javax.xml.bind.JAXBException;
11 | import javax.xml.bind.Marshaller;
12 | import javax.xml.bind.annotation.XmlElement;
13 | import javax.xml.bind.annotation.XmlRootElement;
14 | import java.io.StringWriter;
15 | import java.util.HashMap;
16 | import java.util.List;
17 | import java.util.Map;
18 |
19 | /**
20 | * @author Julian Camargo
21 | */
22 | public class Users extends Resource {
23 | public static final String NEW_USER_PATH = "/protected/json/users/new";
24 | public static final String DELETE_USER_PATH = "/protected/json/users/delete/";
25 | public static final String SMS_PATH = "/protected/json/sms/";
26 | public static final String ONE_CODE_CALL_PATH = "/protected/json/call/";
27 | public static final String USER_STATUS_PATH = "/protected/json/users/%d/status";
28 | public static final String DEFAULT_COUNTRY_CODE = "1";
29 |
30 | public Users(String uri, String key) {
31 | super(uri, key, Resource.JSON_CONTENT_TYPE);
32 | }
33 |
34 | public Users(String uri, String key, boolean testFlag) {
35 | super(uri, key, testFlag, Resource.JSON_CONTENT_TYPE);
36 | }
37 |
38 | /**
39 | * Create a new user using his e-mail, phone and country code.
40 | *
41 | * @param email
42 | * @param phone
43 | * @param countryCode
44 | * @return a User instance
45 | */
46 | public com.authy.api.User createUser(String email, String phone, String countryCode) throws AuthyException {
47 | User user = new User(email, phone, countryCode);
48 | final Response response = this.post(NEW_USER_PATH, user);
49 | return userFromJson(response.getStatus(), response.getBody());
50 | }
51 |
52 | /**
53 | * Create a new user using his e-mail and phone. It uses USA country code by default.
54 | *
55 | * @param email
56 | * @param phone
57 | * @return a User instance
58 | */
59 | public com.authy.api.User createUser(String email, String phone) throws AuthyException {
60 | return createUser(email, phone, DEFAULT_COUNTRY_CODE);
61 | }
62 |
63 | /**
64 | * Send token via sms to a user.
65 | *
66 | * @param userId
67 | * @return Hash instance with API's response.
68 | */
69 | public Hash requestSms(int userId) throws AuthyException {
70 | return requestSms(userId, new HashMap<>(0));
71 | }
72 |
73 | /**
74 | * Send token via sms to a user with some options defined.
75 | *
76 | * @param userId
77 | * @param options
78 | * @return Hash instance with API's response.
79 | */
80 | public Hash requestSms(int userId, Map options) throws AuthyException {
81 | MapToResponse opt = new MapToResponse(options);
82 | final Response response = this.get(SMS_PATH + Integer.toString(userId), opt);
83 | return instanceFromJson(response.getStatus(), response.getBody());
84 | }
85 |
86 | /**
87 | * Send token via call to a user.
88 | *
89 | * @param userId
90 | * @return Hash instance with API's response.
91 | */
92 | public Hash requestCall(int userId) throws AuthyException {
93 | return requestCall(userId, new HashMap<>(0));
94 | }
95 |
96 | /**
97 | * Send token via call to a user with some options defined.
98 | *
99 | * @param userId
100 | * @param options
101 | * @return Hash instance with API's response.
102 | */
103 | public Hash requestCall(int userId, Map options) throws AuthyException {
104 | MapToResponse opt = new MapToResponse(options);
105 | final Response response = this.get(ONE_CODE_CALL_PATH + Integer.toString(userId), opt);
106 | return instanceFromJson(response.getStatus(), response.getBody());
107 | }
108 |
109 | /**
110 | * Delete a user.
111 | *
112 | * @param userId
113 | * @return Hash instance with API's response.
114 | */
115 | public Hash deleteUser(int userId) throws AuthyException {
116 | final Response response = this.post(DELETE_USER_PATH + Integer.toString(userId), null);
117 | return instanceFromJson(response.getStatus(), response.getBody());
118 | }
119 |
120 | /**
121 | * Get user status.
122 | *
123 | * @return object containing user status
124 | */
125 | public UserStatus requestStatus(int userId) throws AuthyException {
126 | final Response response = this.get(String.format(USER_STATUS_PATH, userId), null);
127 | UserStatus userStatus = userStatusFromJson(response);
128 | return userStatus;
129 | }
130 |
131 | private com.authy.api.User userFromJson(int status, String content) throws AuthyException {
132 | com.authy.api.User user = new com.authy.api.User(status, content);
133 | if (user.isOk()) {
134 | JSONObject userJson = new JSONObject(content);
135 | user.setId(userJson.getJSONObject("user").getInt("id"));
136 | } else {
137 | Error error = errorFromJson(content);
138 | user.setError(error);
139 | }
140 | return user;
141 | }
142 |
143 | private Hash instanceFromJson(int status, String content) throws AuthyException {
144 | Hash hash = new Hash(status, content);
145 | if (hash.isOk()) {
146 | try {
147 | JSONObject jsonResponse = new JSONObject(content);
148 | String message = jsonResponse.optString("message");
149 | hash.setMessage(message);
150 |
151 | boolean success = jsonResponse.optBoolean("success");
152 | hash.setSuccess(success);
153 |
154 | String token = jsonResponse.optString("token");
155 | hash.setToken(token);
156 | } catch (JSONException e) {
157 | throw new AuthyException("Invalid response from server", e);
158 | }
159 | } else {
160 | Error error = errorFromJson(content);
161 | hash.setError(error);
162 | }
163 |
164 | return hash;
165 | }
166 |
167 | private UserStatus userStatusFromJson(Response response) throws AuthyException {
168 | UserStatus userStatus = new UserStatus(response.getStatus(), response.getBody());
169 | if (userStatus.isOk()) {
170 | try {
171 | JSONObject jsonResponse = new JSONObject(response.getBody());
172 | String message = jsonResponse.optString("message");
173 | userStatus.setMessage(message);
174 |
175 | boolean success = jsonResponse.optBoolean("success");
176 | userStatus.setSuccess(success);
177 |
178 | JSONObject status = jsonResponse.getJSONObject("status");
179 | int userId = status.getInt("authy_id");
180 | userStatus.setUserId(userId);
181 |
182 | boolean confirmed = status.getBoolean("confirmed");
183 | userStatus.setConfirmed(confirmed);
184 |
185 | boolean registered = status.getBoolean("registered");
186 | userStatus.setRegistered(registered);
187 |
188 | int countryCode = status.getInt("country_code");
189 | userStatus.setCountryCode(countryCode);
190 |
191 | String phoneNumber = status.getString("phone_number");
192 | userStatus.setPhoneNumber(phoneNumber);
193 |
194 | JSONArray devicesArray = status.getJSONArray("devices");
195 | List devices = userStatus.getDevices();
196 | for (int i = 0; i < devicesArray.length(); i++) {
197 | devices.add(devicesArray.getString(i));
198 | }
199 |
200 | } catch (JSONException e) {
201 | throw new AuthyException("Invalid response from server", e);
202 | }
203 | } else {
204 | Error error = errorFromJson(response.getBody());
205 | userStatus.setError(error);
206 | }
207 |
208 | return userStatus;
209 | }
210 |
211 | static class MapToResponse implements Formattable {
212 | private Map options;
213 |
214 | MapToResponse(Map options) {
215 | this.options = options;
216 | }
217 |
218 | public String toXML() {
219 | return "";
220 | }
221 |
222 | public Map toMap() {
223 | return options;
224 | }
225 | }
226 |
227 | @XmlRootElement(name = "user")
228 | static class User implements Formattable {
229 | String email, cellphone, countryCode;
230 |
231 | public User() {
232 | }
233 |
234 | public User(String email, String cellphone, String countryCode) {
235 | this.email = email;
236 | this.cellphone = cellphone;
237 | this.countryCode = countryCode;
238 | }
239 |
240 | @XmlElement(name = "email")
241 | public String getEmail() {
242 | return email;
243 | }
244 |
245 | public void setEmail(String email) {
246 | this.email = email;
247 | }
248 |
249 | @XmlElement(name = "cellphone")
250 | public String getCellphone() {
251 | return cellphone;
252 | }
253 |
254 | public void setCellphone(String cellphone) {
255 | this.cellphone = cellphone;
256 | }
257 |
258 | @XmlElement(name = "country_code")
259 | public String getCountryCode() {
260 | return countryCode;
261 | }
262 |
263 | public void setCountryCode(String countryCode) {
264 | this.countryCode = countryCode;
265 | }
266 |
267 | public String toXML() {
268 | StringWriter sw = new StringWriter();
269 | String xml = "";
270 |
271 | try {
272 | JAXBContext context = JAXBContext.newInstance(this.getClass());
273 | Marshaller marshaller = context.createMarshaller();
274 |
275 | marshaller.marshal(this, sw);
276 |
277 | xml = sw.toString();
278 | } catch (JAXBException e) {
279 | e.printStackTrace();
280 | }
281 | return xml;
282 | }
283 |
284 | public Map toMap() {
285 |
286 | Map map = new HashMap<>();
287 | map.put("email", email);
288 | map.put("cellphone", cellphone);
289 | map.put("country_code", countryCode);
290 |
291 | return map;
292 | }
293 |
294 | @Override
295 | public String toJSON() {
296 | JSONObject json = new JSONObject();
297 | json.put("user", toMap());
298 | return json.toString();
299 | }
300 | }
301 | }
302 |
--------------------------------------------------------------------------------
/src/main/java/com/authy/api/Verification.java:
--------------------------------------------------------------------------------
1 | package com.authy.api;
2 |
3 | import org.json.JSONObject;
4 |
5 | import javax.xml.bind.annotation.XmlElement;
6 | import javax.xml.bind.annotation.XmlRootElement;
7 | import java.util.HashMap;
8 | import java.util.Map;
9 |
10 | /**
11 | * @author Moisés Vargas
12 | */
13 | @XmlRootElement(name = "verification")
14 | public class Verification extends Instance implements Formattable {
15 | private boolean isPorted = false;
16 | private boolean isCellphone = false;
17 |
18 | public Verification() {
19 | }
20 |
21 | public Verification(int status, String response) {
22 | this(status, response, null);
23 | }
24 |
25 | public Verification(int status, String response, String message) {
26 | super(status, response, message);
27 | this.setResponse(response);
28 | }
29 |
30 | @XmlElement(name = "message")
31 | public String getMessage() {
32 | return message;
33 | }
34 |
35 | @XmlElement(name = "success")
36 | public String getSuccess() {
37 | return Boolean.toString(this.isOk());
38 | }
39 |
40 | @XmlElement(name = "is_ported")
41 | public String getIsPorted() {
42 | return Boolean.toString(this.isPorted);
43 | }
44 |
45 | @XmlElement(name = "is_cellphone")
46 | public String getIsCellphone() {
47 | return Boolean.toString(this.isCellphone);
48 | }
49 |
50 | public void setStatus(int status) {
51 | this.status = status;
52 | }
53 |
54 | public void setResponse(String response) {
55 | this.content = response;
56 | JSONObject jsonResponse = new JSONObject(response);
57 | this.parseResponseToOjbect(jsonResponse);
58 | }
59 |
60 | /**
61 | * Map a Token instance to its Java's Map representation.
62 | *
63 | * @return a Java's Map with the description of this object.
64 | */
65 | public Map toMap() {
66 | Map map = new HashMap<>();
67 |
68 | map.put("message", this.getMessage());
69 | map.put("success", this.getSuccess());
70 | map.put("is_ported", this.getIsPorted());
71 | map.put("is_cellphone", this.getIsCellphone());
72 |
73 | return map;
74 | }
75 |
76 | private void parseResponseToOjbect(JSONObject json) {
77 | if (!json.isNull("message"))
78 | this.message = json.getString("message");
79 |
80 | if (!json.isNull("is_ported"))
81 | this.isPorted = json.getBoolean("is_ported");
82 |
83 | if (!json.isNull("is_cellphone"))
84 | this.isCellphone = json.getBoolean("is_cellphone");
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/src/test/java/com/authy/TestAuthyUtil.java:
--------------------------------------------------------------------------------
1 | package com.authy;
2 |
3 | import org.junit.Test;
4 |
5 | import java.io.UnsupportedEncodingException;
6 | import java.net.URLDecoder;
7 | import java.util.AbstractMap.SimpleImmutableEntry;
8 | import java.util.Arrays;
9 | import java.util.HashMap;
10 | import java.util.Map;
11 |
12 | import static java.util.stream.Collectors.toMap;
13 | import static org.hamcrest.core.Is.is;
14 | import static org.junit.Assert.assertThat;
15 |
16 | /**
17 | * Unit tests for {@link com.authy.AuthyUtil}
18 | *
19 | * @author hansospina
20 | *
21 | * Copyright © 2016 Twilio, Inc. All Rights Reserved.
22 | */
23 | public class TestAuthyUtil {
24 | private final String testApikey = "FU10H0uCafgKnXvPfDm5iAisVuHDgXJA";
25 | private final String testCallbackUrl = "https://requestb.in/ui9vdiui";
26 |
27 |
28 | @Test
29 | public void testValidSignatureApprovedPost() throws UnsupportedEncodingException, OneTouchException {
30 | final String testNonce = "1512923419";
31 | final String expectedSignature = "6uKAaKxCkkFEyQujwdudyPNUgRi2fY5otoCOQN7VjCw=";
32 | final String callbackBody = "{\"authy_id\":688611,\"device_uuid\":2102943,\"callback_action\":\"approval_request_status\",\"uuid\":\"4a725520-bff5-0135-eb01-0e5d90336a8c\",\"status\":\"approved\",\"approval_request\":{\"transaction\":{\"details\":null,\"device_details\":{},\"device_geolocation\":\"\",\"device_signing_time\":1512923419,\"encrypted\":false,\"flagged\":false,\"hidden_details\":null,\"message\":\"Authorize OneTouch Unit Test\",\"reason\":\"\",\"requester_details\":null,\"status\":\"approved\",\"uuid\":\"4a725520-bff5-0135-eb01-0e5d90336a8c\",\"created_at_time\":1512923400,\"customer_uuid\":15083},\"expiration_timestamp\":1513009800,\"logos\":null,\"app_id\":\"582380eb7e1caa03317ec08c\"},\"signature\":\"pQmypvnmoIb7qIHrKcd3nwPArh8Ecr8L87XTYqqAfagDkXhOD7CulSdDkE0ImFBzigwc+vxwZsgxBnhFmU2c9kYuvMJiPLeS8NCpRucg7eHeGPM0jQbKveqzFcZ9L6P1kRHjSYwS7dLqEINBffNckK7O9LHz13XYklxXvYUwvemtj+yEemyCJbmlJbCSlUTyajr3WSRPMYZV7xXTNtWp2XvcRSclP8izgA1cV/cw7ctDYIPG6wUXJGSIs/kg3hTDeN1Z3YBq1fnMkfxeb5g9bRveRlCjXpQ6xFh1wQEUbbtJpf+uRgxddbQwxfda9gIb5osOEhKRv5HoJ03yOvKiBQ==\",\"device\":{\"city\":null,\"country\":\"Colombia\",\"ip\":\"190.130.65.251\",\"region\":null,\"registration_city\":null,\"registration_country\":\"Colombia\",\"registration_ip\":\"190.130.66.242\",\"registration_method\":\"sms\",\"registration_region\":null,\"os_type\":\"android\",\"last_account_recovery_at\":null,\"id\":2102943,\"registration_date\":1505418165,\"last_sync_date\":1505418173}}";
33 |
34 | Map headers = createHeaders(testNonce, expectedSignature);
35 | final boolean valid = AuthyUtil.validateSignatureForPost(callbackBody, headers, testCallbackUrl, testApikey);
36 |
37 | assertThat(valid, is(true));
38 | }
39 |
40 | @Test
41 | public void testValidSignatureApprovedWithUnlockMethodPost() throws UnsupportedEncodingException, OneTouchException {
42 | final String testNonce = "1570641874";
43 | final String expectedSignature = "QZL9dnpVH5bjPMRvC98pm9zsQ2Zv1PToXlpN5X8H1hI=";
44 | final String callbackBody = "{\"authy_id\":688611,\"device_uuid\":2102943,\"callback_action\":\"approval_request_status\",\"uuid\":\"4a725520-bff5-0135-eb01-0e5d90336a8c\",\"status\":\"approved\",\"approval_request\":{\"transaction\":{\"details\":{\"Username\":\"jdoe\",\"Location\":\"earth\",\"Process ID\":\"1234567890\"},\"device_details\":{},\"device_geolocation\":\"\",\"device_signing_time\":1512923419,\"encrypted\":false,\"flagged\":false,\"hidden_details\":{\"transaction_id\":\"TR139872562346\"},\"message\":\"Authorize OneTouch Unit Test\",\"reason\":\"\",\"requester_details\":null,\"status\":\"approved\",\"uuid\":\"4a725520-bff5-0135-eb01-0e5d90336a8c\",\"created_at_time\":1512923400,\"customer_uuid\":15083},\"expiration_timestamp\":1513009800,\"logos\":null,\"app_id\":\"582380eb7e1caa03317ec08c\"},\"signature\":\"pQmypvnmoIb7qIHrKcd3nwPArh8Ecr8L87XTYqqAfagDkXhOD7CulSdDkE0ImFBzigwc+vxwZsgxBnhFmU2c9kYuvMJiPLeS8NCpRucg7eHeGPM0jQbKveqzFcZ9L6P1kRHjSYwS7dLqEINBffNckK7O9LHz13XYklxXvYUwvemtj+yEemyCJbmlJbCSlUTyajr3WSRPMYZV7xXTNtWp2XvcRSclP8izgA1cV/cw7ctDYIPG6wUXJGSIs/kg3hTDeN1Z3YBq1fnMkfxeb5g9bRveRlCjXpQ6xFh1wQEUbbtJpf+uRgxddbQwxfda9gIb5osOEhKRv5HoJ03yOvKiBQ==\",\"device\":{\"city\":null,\"country\":\"Colombia\",\"enabled_unlock_methods\":[\"pin\",\"fingerprint\"],\"ip\":\"190.130.65.251\",\"last_unlock_method_used\":\"pin\",\"region\":null,\"registration_city\":null,\"registration_country\":\"Colombia\",\"registration_ip\":\"190.130.66.242\",\"registration_method\":\"sms\",\"registration_region\":null,\"os_type\":\"android\",\"last_account_recovery_at\":null,\"multidevice_enabled\":true,\"multidevice_updated_at\":1570469080,\"id\":2102943,\"registration_date\":1505418165,\"last_sync_date\":1505418173,\"last_unlock_date\":1570641596}}";
45 |
46 | Map headers = createHeaders(testNonce, expectedSignature);
47 | final boolean valid = AuthyUtil.validateSignatureForPost(callbackBody, headers, testCallbackUrl, testApikey);
48 |
49 | assertThat(valid, is(true));
50 | }
51 |
52 | @Test
53 | public void testValidSignatureDeniedWithDetailsPost() throws UnsupportedEncodingException, OneTouchException {
54 | final String testNonce = "1512940231";
55 | final String expectedSignature = "HFc5mICOVRrEmpmBoIuubKWcN0YYd50TaO7YQJFrnlM=";
56 | final String callbackBody = "{\"authy_id\":688611,\"device_uuid\":2102943,\"callback_action\":\"approval_request_status\",\"uuid\":\"6edb8f40-c01c-0135-c648-0e00ace7f69c\",\"status\":\"denied\",\"approval_request\":{\"transaction\":{\"details\":{\"username\":\"User\",\"location\":\"California,USA\",\"cosa1\":\"cosa1\",\"cosa2\":\"cosa2\"},\"device_details\":{},\"device_geolocation\":\"\",\"device_signing_time\":1512940232,\"encrypted\":false,\"flagged\":false,\"hidden_details\":{\"ip_address\":\"10.10.3.203\"},\"message\":\"Authorize OneTouch Unit Test\",\"reason\":\"\",\"requester_details\":null,\"status\":\"denied\",\"uuid\":\"6edb8f40-c01c-0135-c648-0e00ace7f69c\",\"created_at_time\":1512940211,\"customer_uuid\":15083},\"expiration_timestamp\":1513026611,\"logos\":null,\"app_id\":\"582380eb7e1caa03317ec08c\"},\"signature\":\"jTLeK9tHUAhJRexjBIq3DVowPRKE+8578YzJD5yizXqYqmeQT7t8NS1uFfbmjHKabgIvC0N/WEFfyvKWjARrNVR6FJ5EjbOZJy7ouQT+9iTaorsJDVPUPeVnQUUTi3noXcSonGN0+YW7foHf8zMnTTyQBbjurexv2dkfu0fLdiF8I6xRhMeq5sf5APdZCt7NsFIM95N0wO6MHoD5sLL8yFrB1C/RB35n6BIxgTWz0TjtbcO+V/rqjgMK47xTITPsbEo46ammhRl4vU5flcM2O6KE6Q7tKLCftAzs/3xu13w1KKkFXmCqXpeB29lSNU2wveGI7nB2eIk41medDJ81Dg==\",\"device\":{\"city\":null,\"country\":\"Colombia\",\"ip\":\"190.130.65.251\",\"region\":null,\"registration_city\":null,\"registration_country\":\"Colombia\",\"registration_ip\":\"190.130.66.242\",\"registration_method\":\"sms\",\"registration_region\":null,\"os_type\":\"android\",\"last_account_recovery_at\":null,\"id\":2102943,\"registration_date\":1505418165,\"last_sync_date\":1505418173}}";
57 |
58 | Map headers = createHeaders(testNonce, expectedSignature);
59 | final boolean valid = AuthyUtil.validateSignatureForPost(callbackBody, headers, testCallbackUrl, testApikey);
60 |
61 | assertThat(valid, is(true));
62 | }
63 |
64 | @Test
65 | public void testValidSignatureApprovedWithDetailsAndLogosPost() throws UnsupportedEncodingException, OneTouchException {
66 | final String testNonce = "1512940917";
67 | final String expectedSignature = "IFD2P2f5w2Jis1uref9Blu7a0liztOsnso16Gh6y054=";
68 | final String callbackBody = "{\"authy_id\":688611,\"device_uuid\":2102943,\"callback_action\":\"approval_request_status\",\"uuid\":\"098c3580-c01e-0135-eb00-0e5d90336a8c\",\"status\":\"approved\",\"approval_request\":{\"transaction\":{\"details\":{\"username\":\"User\",\"location\":\"California,USA\",\"cosa1\":\"cosa1\",\"cosa2\":\"cosa2\"},\"device_details\":{},\"device_geolocation\":\"\",\"device_signing_time\":1512940918,\"encrypted\":false,\"flagged\":false,\"hidden_details\":{\"ip_address\":\"10.10.3.203\"},\"message\":\"Authorize OneTouch Unit Test\",\"reason\":\"\",\"requester_details\":null,\"status\":\"approved\",\"uuid\":\"098c3580-c01e-0135-eb00-0e5d90336a8c\",\"created_at_time\":1512940900,\"customer_uuid\":15083},\"expiration_timestamp\":1513027300,\"logos\":[{\"res\":\"default\",\"url\":\"https://www.itsalllost.com/wp-content/uploads/2017/04/twilio-logo-red.png\"},{\"res\":\"med\",\"url\":\"https://www.itsalllost.com/wp-content/uploads/2017/04/twilio-logo-red.png\"}],\"app_id\":\"582380eb7e1caa03317ec08c\"},\"signature\":\"qOLMuHVy4KITm2nSd1PxJCv+ydjcduKwxz2Fc7pMrDm7QtU2hMAnY5AUxdwlae5WJmEWNM8OctdGhMJweTwICkkOgYm2v+u7k/wz5zuPozDDnMqJWBjiCfbKNpKqf8CQ2dBndtxi2Sl7/y57KiXYJfTlGHNBhCoTxVzVBNDEPu6OLV6KA60mcEW87tg1b4Q/p69ZkYb5B1f9Ujk/ueCbCK6JDhtUf1v3/baNgO8o/mp2EydiFughqpiHIIOR0VY9/o/hh5a7z6FG4OxZ3WmS7q2506Wy698LW3ZRNl0aXwtFIat4IyCSSuDQFW9LZEcXdQK4YixSJ8b5H8vlRErSPQ==\",\"device\":{\"city\":null,\"country\":\"Colombia\",\"ip\":\"190.130.65.251\",\"region\":null,\"registration_city\":null,\"registration_country\":\"Colombia\",\"registration_ip\":\"190.130.66.242\",\"registration_method\":\"sms\",\"registration_region\":null,\"os_type\":\"android\",\"last_account_recovery_at\":null,\"id\":2102943,\"registration_date\":1505418165,\"last_sync_date\":1505418173}}";
69 |
70 | Map headers = createHeaders(testNonce, expectedSignature);
71 | final boolean valid = AuthyUtil.validateSignatureForPost(callbackBody, headers, testCallbackUrl, testApikey);
72 |
73 | assertThat(valid, is(true));
74 | }
75 |
76 | @Test
77 | public void testValidSignatureApprovedGet() throws UnsupportedEncodingException, OneTouchException {
78 | final String testNonce = "1512937258";
79 | final String expectedSignature = "W0YumwSPoL+2CX1q4XoUSY5gOmWmnrkXUmlm7NE6iGY=";
80 | final String callbackQueryString = "approval_request%5Bapp_id%5D=582380eb7e1caa03317ec08c&approval_request%5Bexpiration_timestamp%5D=1513023630&approval_request%5Blogos%5D=&approval_request%5Btransaction%5D%5Bcreated_at_time%5D=1512937230&approval_request%5Btransaction%5D%5Bcustomer_uuid%5D=15083&approval_request%5Btransaction%5D%5Bdetails%5D=&approval_request%5Btransaction%5D%5Bdevice_geolocation%5D=&approval_request%5Btransaction%5D%5Bdevice_signing_time%5D=1512937257&approval_request%5Btransaction%5D%5Bencrypted%5D=false&approval_request%5Btransaction%5D%5Bflagged%5D=false&approval_request%5Btransaction%5D%5Bhidden_details%5D=&approval_request%5Btransaction%5D%5Bmessage%5D=Authorize+OneTouch+Unit+Test&approval_request%5Btransaction%5D%5Breason%5D=&approval_request%5Btransaction%5D%5Brequester_details%5D=&approval_request%5Btransaction%5D%5Bstatus%5D=approved&approval_request%5Btransaction%5D%5Buuid%5D=7e272da0-c015-0135-eafc-0e5d90336a8c&authy_id=688611&callback_action=approval_request_status&device%5Bcity%5D=&device%5Bcountry%5D=Colombia&device%5Bid%5D=2102943&device%5Bip%5D=190.130.65.251&device%5Blast_account_recovery_at%5D=&device%5Blast_sync_date%5D=1505418173&device%5Bos_type%5D=android&device%5Bregion%5D=&device%5Bregistration_city%5D=&device%5Bregistration_country%5D=Colombia&device%5Bregistration_date%5D=1505418165&device%5Bregistration_ip%5D=190.130.66.242&device%5Bregistration_method%5D=sms&device%5Bregistration_region%5D=&device_uuid=2102943&signature=YnZe2qSWEAYAROAhykwbeIOV2Ym%2Fg4y9rlIQc6ePtvTt9UDDotl7p2H%2FpC3EFNG5XsDaMkJuZmXSd0UW%2FtiuR2l%2BJ%2Fvta5yXVArq6d1uNspB1u%2BWYjumDhTLFQI0Ox6BGQMhTlWkQK96dh0bJQqyPP7I5f4xQZMmNIClCKZa%2BzUqmPA7zo1Qokz9w0u917zKt%2BsLLxLLXblhdYvFIvfVqGAtiBQzbUh9UCCuOp6jcF7HkZiGs2nFIAlwVFffhK%2BxXnEXvG9yA3KnMRX6Y31yxWd3ApRoDNbLpCOSgFlkYAasQ8hBkcSy3AyyfT4TMhyeemI3IW6GFTADrXXO%2BzvpcA%3D%3D&status=approved&uuid=7e272da0-c015-0135-eafc-0e5d90336a8c";
81 |
82 | Map headers = createHeaders(testNonce, expectedSignature);
83 | Map params = extractQueryParams(callbackQueryString);
84 | final boolean valid = AuthyUtil.validateSignatureForGet(params, headers, testCallbackUrl, testApikey);
85 |
86 | assertThat(valid, is(true));
87 | }
88 |
89 | @Test
90 | public void testValidSignatureApprovedWithDetailsGet() throws UnsupportedEncodingException, OneTouchException {
91 | final String testNonce = "1512942246";
92 | final String expectedSignature = "oCSbVsRS7uwKH1bRAnyem3pz9rTBSMpcEaS6W33DqmM=";
93 | final String callbackQueryString = "approval_request%5Bapp_id%5D=582380eb7e1caa03317ec08c&approval_request%5Bexpiration_timestamp%5D=1513028630&approval_request%5Blogos%5D=&approval_request%5Btransaction%5D%5Bcreated_at_time%5D=1512942230&approval_request%5Btransaction%5D%5Bcustomer_uuid%5D=15083&approval_request%5Btransaction%5D%5Bdetails%5D%5Bcosa1%5D=cosa1&approval_request%5Btransaction%5D%5Bdetails%5D%5Bcosa2%5D=cosa2&approval_request%5Btransaction%5D%5Bdetails%5D%5Blocation%5D=California%2CUSA&approval_request%5Btransaction%5D%5Bdetails%5D%5Busername%5D=User&approval_request%5Btransaction%5D%5Bdevice_geolocation%5D=&approval_request%5Btransaction%5D%5Bdevice_signing_time%5D=1512942247&approval_request%5Btransaction%5D%5Bencrypted%5D=false&approval_request%5Btransaction%5D%5Bflagged%5D=false&approval_request%5Btransaction%5D%5Bhidden_details%5D%5Bip_address%5D=10.10.3.203&approval_request%5Btransaction%5D%5Bmessage%5D=Authorize+OneTouch+Unit+Test&approval_request%5Btransaction%5D%5Breason%5D=&approval_request%5Btransaction%5D%5Brequester_details%5D=&approval_request%5Btransaction%5D%5Bstatus%5D=approved&approval_request%5Btransaction%5D%5Buuid%5D=224fdf40-c021-0135-fa62-06ca50569adc&authy_id=688611&callback_action=approval_request_status&device%5Bcity%5D=&device%5Bcountry%5D=Colombia&device%5Bid%5D=2102943&device%5Bip%5D=190.130.65.251&device%5Blast_account_recovery_at%5D=&device%5Blast_sync_date%5D=1505418173&device%5Bos_type%5D=android&device%5Bregion%5D=&device%5Bregistration_city%5D=&device%5Bregistration_country%5D=Colombia&device%5Bregistration_date%5D=1505418165&device%5Bregistration_ip%5D=190.130.66.242&device%5Bregistration_method%5D=sms&device%5Bregistration_region%5D=&device_uuid=2102943&signature=rRf5hjOsPql%2Fumb%2FzI6azWUx2Xx8s6hhKeOYXob0tqhIA7WUtUcvXDNfn%2BX%2FnAqOiBDcGr41aNU6mFw1nCbQI3jwtm9n%2F7RVtxrJN%2B85370dY0nOUpl19IGJ6xwyRa0U76svfePBROnrobGdyCvtHw6G4tT%2BJ3oo2T7Ji1TZ4scFLaRfiA95VmFYgJd6tEqoBom%2FtX8itKyxa%2FFTVSc8OriSusyX8GpxqSAKl9GjVKGp7W0p%2FGTzTU9lzVAIPHsIyX9%2FH%2BKUGk5d7oglcPb%2B0HX2V1ruSIg5IId5j3yrBPd%2Bjxf3HLTcrPMGjFZJhapMOwE4fgEWG9p6pfBYOyyauw%3D%3D&status=approved&uuid=224fdf40-c021-0135-fa62-06ca50569adc";
94 |
95 | Map headers = createHeaders(testNonce, expectedSignature);
96 | Map params = extractQueryParams(callbackQueryString);
97 | final boolean valid = AuthyUtil.validateSignatureForGet(params, headers, testCallbackUrl, testApikey);
98 |
99 | assertThat(valid, is(true));
100 | }
101 |
102 | @Test
103 | public void testValidSignatureApprovedWithDetailsAndLogosGet() throws UnsupportedEncodingException, OneTouchException {
104 | final String testNonce = "1512943081";
105 | final String expectedSignature = "5TM5uf+8WlPMjkREHeS39XUHv8CHjhq3/u/+/lWU7cg=";
106 | final String callbackQueryString = "approval_request%5Bapp_id%5D=582380eb7e1caa03317ec08c&approval_request%5Bexpiration_timestamp%5D=1513029464&approval_request%5Blogos%5D%5B%5D%5Bres%5D=default&approval_request%5Blogos%5D%5B%5D%5Burl%5D=https%3A%2F%2Fwww.itsalllost.com%2Fwp-content%2Fuploads%2F2017%2F04%2Ftwilio-logo-red.png&approval_request%5Btransaction%5D%5Bcreated_at_time%5D=1512943064&approval_request%5Btransaction%5D%5Bcustomer_uuid%5D=15083&approval_request%5Btransaction%5D%5Bdetails%5D%5Bcosa1%5D=cosa1&approval_request%5Btransaction%5D%5Bdetails%5D%5Bcosa2%5D=cosa2&approval_request%5Btransaction%5D%5Bdetails%5D%5Blocation%5D=California%2CUSA&approval_request%5Btransaction%5D%5Bdetails%5D%5Busername%5D=User&approval_request%5Btransaction%5D%5Bdevice_geolocation%5D=&approval_request%5Btransaction%5D%5Bdevice_signing_time%5D=1512943081&approval_request%5Btransaction%5D%5Bencrypted%5D=false&approval_request%5Btransaction%5D%5Bflagged%5D=false&approval_request%5Btransaction%5D%5Bhidden_details%5D%5Bip_address%5D=10.10.3.203&approval_request%5Btransaction%5D%5Bmessage%5D=Authorize+OneTouch+Unit+Test&approval_request%5Btransaction%5D%5Breason%5D=&approval_request%5Btransaction%5D%5Brequester_details%5D=&approval_request%5Btransaction%5D%5Bstatus%5D=approved&approval_request%5Btransaction%5D%5Buuid%5D=133c7590-c023-0135-fa61-06ca50569adc&authy_id=688611&callback_action=approval_request_status&device%5Bcity%5D=&device%5Bcountry%5D=Colombia&device%5Bid%5D=2102943&device%5Bip%5D=190.130.65.251&device%5Blast_account_recovery_at%5D=&device%5Blast_sync_date%5D=1505418173&device%5Bos_type%5D=android&device%5Bregion%5D=&device%5Bregistration_city%5D=&device%5Bregistration_country%5D=Colombia&device%5Bregistration_date%5D=1505418165&device%5Bregistration_ip%5D=190.130.66.242&device%5Bregistration_method%5D=sms&device%5Bregistration_region%5D=&device_uuid=2102943&signature=Q%2FTaHEbdfmBBw%2BT%2BgVBrM8Sw%2BdqT%2FnhdO6BJNc%2F7herUg4BxAwziQhdmQhqjY2nvtVJkWEa9PdB11tNjTcbMQs8cchyPPNLUvp8L7C82snxcH%2Fgdde65Z%2B39Aug5hCcXUoQ92PsRckvexkrCQASDWbvmJZnjVM5t3j%2BKXn2QVyZkc63GBE1W9GPNM7bmlL2ZOENPoxpUq9%2B%2FawTEb8WMDebDcFEPGSgNFcvk6%2FJTSwjzmmm8Ypk82s4P1lUq74WWxaDM7XJH2Q4mo9vHkDaRIweEx%2BliMqUQYoCtApD0vsPSe4Exfp5tfcelWE9xrte%2FF3qKiPSnV8xS0SY9L20%2F7A%3D%3D&status=approved&uuid=133c7590-c023-0135-fa61-06ca50569adc";
107 |
108 | Map headers = createHeaders(testNonce, expectedSignature);
109 | Map params = extractQueryParams(callbackQueryString);
110 | final boolean valid = AuthyUtil.validateSignatureForGet(params, headers, testCallbackUrl, testApikey);
111 |
112 | assertThat(valid, is(true));
113 | }
114 |
115 | private Map extractQueryParams(String callbackQueryString) {
116 | return Arrays.stream(callbackQueryString.split("&")).map(it -> {
117 | final int idx = it.indexOf("=");
118 | final String key = URLDecoder.decode(idx > 0 ? it.substring(0, idx) : it);
119 | final String value = URLDecoder.decode(idx > 0 && it.length() > idx + 1 ? it.substring(idx + 1) : "");
120 | return new SimpleImmutableEntry<>(key, value);
121 | }).collect(toMap(SimpleImmutableEntry::getKey, SimpleImmutableEntry::getValue));
122 | }
123 |
124 | private Map createHeaders(String nonce, String signature) {
125 | Map headers = new HashMap<>();
126 | headers.put(AuthyUtil.HEADER_AUTHY_SIGNATURE_NONCE, nonce);
127 | headers.put(AuthyUtil.HEADER_AUTHY_SIGNATURE, signature);
128 | return headers;
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/src/test/java/com/authy/api/TestApiBase.java:
--------------------------------------------------------------------------------
1 | package com.authy.api;
2 |
3 | import com.github.tomakehurst.wiremock.junit.WireMockRule;
4 | import org.junit.Rule;
5 |
6 | public class TestApiBase {
7 | @Rule
8 | public WireMockRule wireMockRule = new WireMockRule(18089);
9 |
10 | protected final String testHost = "http://localhost:18089";
11 | protected final String testApiKey = "test_api_key";
12 | }
13 |
--------------------------------------------------------------------------------
/src/test/java/com/authy/api/TestOneTouch.java:
--------------------------------------------------------------------------------
1 | package com.authy.api;
2 |
3 | import static com.authy.api.Error.Code.ONETOUCH_APPROVAL_REQUEST_NOT_FOUND;
4 | import static com.authy.api.Error.Code.USER_NOT_FOUND;
5 | import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
6 | import static com.github.tomakehurst.wiremock.client.WireMock.equalTo;
7 | import static com.github.tomakehurst.wiremock.client.WireMock.equalToJson;
8 | import static com.github.tomakehurst.wiremock.client.WireMock.get;
9 | import static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor;
10 | import static com.github.tomakehurst.wiremock.client.WireMock.post;
11 | import static com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor;
12 | import static com.github.tomakehurst.wiremock.client.WireMock.stubFor;
13 | import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo;
14 | import static com.github.tomakehurst.wiremock.client.WireMock.verify;
15 |
16 | import static junit.framework.TestCase.assertEquals;
17 | import static junit.framework.TestCase.assertFalse;
18 |
19 | import com.authy.AuthyApiClient;
20 | import com.authy.AuthyException;
21 | import com.authy.OneTouchException;
22 | import com.authy.api.ApprovalRequestParams.Resolution;
23 |
24 | import org.junit.Assert;
25 | import org.junit.Before;
26 | import org.junit.Rule;
27 | import org.junit.Test;
28 | import org.junit.rules.ExpectedException;
29 |
30 | /**
31 | * Unit tests for the API described at: http://docs.authy.com/onetouch.html#onetouch-api
32 | *
33 | * @author hansospina
34 | *
35 | * Copyright © 2017 Twilio, Inc. All Rights Reserved.
36 | */
37 | public class TestOneTouch extends TestApiBase {
38 |
39 | final private String testUserId = "30144611";
40 | final private String testOneTouchUUID = "8d031630-d15b-0134-b0a8-0a77bbe8093e";
41 | final private String successStatusResponse = "{" +
42 | " \"approval_request\": {" +
43 | " \"_app_name\": \"testhans\"," +
44 | " \"_app_serial_id\": 46113," +
45 | " \"_authy_id\": 28894864," +
46 | " \"_id\": \"589d12e07d558e6b91e9370a\"," +
47 | " \"_user_email\": \"hans+1@allcode.com\"," +
48 | " \"app_id\": \"58547f6f4014250a11c201f6\"," +
49 | " \"created_at\": \"2017-02-10T01:09:52Z\"," +
50 | " \"notified\": false," +
51 | " \"processed_at\": \"2017-02-13T11:11:58Z\"," +
52 | " \"seconds_to_expire\": 86400," +
53 | " \"status\": \"expired\"," +
54 | " \"updated_at\": \"2017-02-13T11:11:58Z\"," +
55 | " \"user_id\": \"5840a9328aa2fb674c485436\"," +
56 | " \"uuid\": \"8d031630-d15b-0134-b0a8-0a77bbe8093e\"" +
57 | " }," +
58 | " \"success\": true" +
59 | "}";
60 |
61 | final private String successSendApprovalRequestResponse = "{" +
62 | " \"approval_request\": {" +
63 | " \"uuid\": \"dda2c400-bc43-0135-d7be-1285ca17e122\"" +
64 | " }," +
65 | " \"success\": true" +
66 | "}";
67 |
68 | final private String userNotFoundResponse = "{"
69 | + " \"message\": \"User not found.\","
70 | + " \"success\": false,"
71 | + " \"errors\": {"
72 | + " \"message\": \"User not found.\""
73 | + " },"
74 | + " \"error_code\": \"60026\""
75 | + "}";
76 |
77 | final private String approvalRequestNotFound = "{"
78 | + " \"message\": \"Approval request not found: 3f05a350-4b2c-0136-f779-12c0c2bf9easd\","
79 | + " \"success\": false,"
80 | + " \"errors\": {},"
81 | + " \"error_code\": \"60049\""
82 | + "}";
83 |
84 | @Rule
85 | public ExpectedException thrown = ExpectedException.none();
86 | private AuthyApiClient client;
87 |
88 | @Before
89 | public void setUp() {
90 | client = new AuthyApiClient(testApiKey, testHost, true);
91 | }
92 |
93 | @Test
94 | public void testEmptyMessage() throws OneTouchException {
95 | thrown.expect(OneTouchException.class);
96 | thrown.expectMessage(ApprovalRequestParams.Builder.MESSAGE_ERROR);
97 |
98 | ApprovalRequestParams approvalRequestParams = new ApprovalRequestParams.Builder(Integer.parseInt(testUserId), "")
99 | .addDetail("username", "User")
100 | .addDetail("location", "California,USA")
101 | .addHiddenDetail("ip_address", "10.10.3.203")
102 | .addLogo(Resolution.Default, "http://image.co").build();
103 |
104 | Assert.fail();
105 | }
106 |
107 | @Test
108 | public void testAuthNull() throws OneTouchException {
109 | thrown.expect(OneTouchException.class);
110 | thrown.expectMessage(ApprovalRequestParams.Builder.AUTHYID_ERROR);
111 |
112 | ApprovalRequestParams approvalRequestParams = new ApprovalRequestParams.Builder(null, "Authorize OneTouch Unit Test")
113 | .addDetail("username", "User")
114 | .addDetail("location", "California,USA")
115 | .addHiddenDetail("ip_address", "10.10.3.203")
116 | .addLogo(Resolution.Default, "http://image.co")
117 | .build();
118 |
119 | Assert.fail();
120 | }
121 |
122 | @Test
123 | public void testSendApprovalRequestOk() throws AuthyException {
124 | stubFor(post(urlPathEqualTo("/onetouch/json/users/" + testUserId + "/approval_requests"))
125 | .willReturn(aResponse()
126 | .withStatus(200)
127 | .withHeader("Content-Type", "application/json;charset=utf-8")
128 | .withBody(successSendApprovalRequestResponse)));
129 |
130 | ApprovalRequestParams approvalRequestParams = new ApprovalRequestParams.Builder(Integer.parseInt(testUserId), "Authorize OneTouch Unit Test")
131 | .addDetail("username", "User")
132 | .addDetail("location", "California,USA")
133 | .addHiddenDetail("ip_address", "10.10.3.203")
134 | .addLogo(Resolution.Low, "http://image.low")
135 | .addLogo(Resolution.Medium, "http://image.co")
136 | .addLogo(Resolution.Default, "http://image.co")
137 | .build();
138 |
139 | OneTouchResponse response = client.getOneTouch().sendApprovalRequest(approvalRequestParams);
140 |
141 | verify(postRequestedFor(urlPathEqualTo("/onetouch/json/users/" + testUserId + "/approval_requests"))
142 | .withHeader("X-Authy-API-Key", equalTo(testApiKey))
143 | .withRequestBody(equalToJson("{" +
144 | " \"hidden_details\" : {" +
145 | " \"ip_address\" : \"10.10.3.203\"" +
146 | " }," +
147 | " \"details\" : {" +
148 | " \"location\" : \"California,USA\"," +
149 | " \"username\" : \"User\"" +
150 | " }," +
151 | " \"message\" : \"Authorize OneTouch Unit Test\"," +
152 | " \"logos\" : [ {" +
153 | " \"res\" : \"med\"," +
154 | " \"url\" : \"http://image.co\"" +
155 | " }, {" +
156 | " \"res\" : \"low\"," +
157 | " \"url\" : \"http://image.low\"" +
158 | " }, {" +
159 | " \"res\" : \"default\"," +
160 | " \"url\" : \"http://image.co\"" +
161 | " } ]" +
162 | "}", true, true)));
163 | Assert.assertTrue(response.isSuccess());
164 | }
165 |
166 | @Test
167 | public void testBadDetail() throws OneTouchException {
168 | thrown.expect(OneTouchException.class);
169 | thrown.expectMessage(ApprovalRequestParams.Builder.DETAIL_ERROR);
170 |
171 | ApprovalRequestParams approvalRequestParams = new ApprovalRequestParams.Builder(Integer.parseInt(testUserId), "Authorize OneTouch Unit Test")
172 | .addDetail("", "")
173 | .addHiddenDetail("ip_address", "10.10.3.203")
174 | .addLogo(Resolution.Default, "http://image.co")
175 | .build();
176 |
177 | Assert.fail();
178 | }
179 |
180 | @Test
181 | public void testHiddenDetail() throws OneTouchException {
182 | thrown.expect(OneTouchException.class);
183 | thrown.expectMessage(ApprovalRequestParams.Builder.HIDDEN_DETAIL_ERROR);
184 |
185 | ApprovalRequestParams approvalRequestParams = new ApprovalRequestParams.Builder(Integer.parseInt(testUserId), "Authorize OneTouch Unit Test")
186 | .addDetail("username", "User")
187 | .addDetail("location", "California,USA")
188 | .addHiddenDetail("ip_address", null)
189 | .addLogo(Resolution.Default, "http://image.co")
190 | .build();
191 |
192 | Assert.fail();
193 | }
194 |
195 | @Test
196 | public void testDefaultLogo() throws OneTouchException {
197 | thrown.expect(OneTouchException.class);
198 | thrown.expectMessage(ApprovalRequestParams.Builder.LOGO_ERROR_DEFAULT);
199 |
200 | ApprovalRequestParams approvalRequestParams = new ApprovalRequestParams.Builder(Integer.parseInt(testUserId), "Authorize OneTouch Unit Test")
201 | .addDetail("username", "User")
202 | .addDetail("location", "California,USA")
203 | .addLogo(Resolution.Low, "http://image.co")
204 | .build();
205 |
206 | Assert.fail();
207 | }
208 |
209 |
210 | @Test
211 | public void testGetApprovalRequestStatus() throws Exception {
212 | stubFor(get(urlPathEqualTo("/onetouch/json/approval_requests/" + testOneTouchUUID))
213 | .willReturn(aResponse()
214 | .withStatus(200)
215 | .withHeader("Content-Type", "application/json;charset=utf-8")
216 | .withBody(successStatusResponse)));
217 |
218 | OneTouchResponse response = client.getOneTouch().getApprovalRequestStatus(testOneTouchUUID);
219 |
220 | verify(getRequestedFor(urlPathEqualTo("/onetouch/json/approval_requests/" + testOneTouchUUID))
221 | .withHeader("X-Authy-API-Key", equalTo(testApiKey)));
222 | Assert.assertTrue(response.isSuccess());
223 | Assert.assertNotNull(response.getApprovalRequest().getStatus());
224 | }
225 |
226 | @Test
227 | public void testUserNotFoundError() throws Exception {
228 | String invalidUserId = "12342325";
229 | stubFor(post(urlPathEqualTo("/onetouch/json/users/" + invalidUserId + "/approval_requests"))
230 | .willReturn(aResponse()
231 | .withStatus(404)
232 | .withHeader("Content-Type", "application/json;charset=utf-8")
233 | .withBody(userNotFoundResponse)));
234 |
235 | ApprovalRequestParams approvalRequestParams = new ApprovalRequestParams.Builder(Integer.parseInt(invalidUserId), "Authorize OneTouch Unit Test")
236 | .addDetail("username", "User")
237 | .addDetail("location", "California,USA")
238 | .addHiddenDetail("ip_address", "10.10.3.203")
239 | .addLogo(Resolution.Low, "http://image.low")
240 | .addLogo(Resolution.Medium, "http://image.co")
241 | .addLogo(Resolution.Default, "http://image.co")
242 | .build();
243 |
244 | OneTouchResponse response = client.getOneTouch().sendApprovalRequest(approvalRequestParams);
245 |
246 | assertFalse(response.isSuccess());
247 | assertEquals(USER_NOT_FOUND, response.getError().getCode());
248 | }
249 |
250 | @Test
251 | public void testApprovalRequestNotFoundError() throws Exception {
252 | String invalidOneTouchUUID = "3f05a350-4b2c-0136-f779-12c0c2bf9easd";
253 | stubFor(get(urlPathEqualTo("/onetouch/json/approval_requests/" + invalidOneTouchUUID))
254 | .willReturn(aResponse()
255 | .withStatus(404)
256 | .withHeader("Content-Type", "application/json;charset=utf-8")
257 | .withBody(approvalRequestNotFound)));
258 |
259 | OneTouchResponse response = client.getOneTouch().getApprovalRequestStatus(invalidOneTouchUUID);
260 |
261 | assertFalse(response.isSuccess());
262 | assertEquals(ONETOUCH_APPROVAL_REQUEST_NOT_FOUND, response.getError().getCode());
263 | assertEquals("60049", response.getErrorCode());
264 | }
265 | }
266 |
--------------------------------------------------------------------------------
/src/test/java/com/authy/api/TestPhoneInfo.java:
--------------------------------------------------------------------------------
1 | package com.authy.api;
2 |
3 | import static com.authy.api.Error.Code.PHONE_INFO_ERROR_QUERYING;
4 | import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
5 | import static com.github.tomakehurst.wiremock.client.WireMock.equalTo;
6 | import static com.github.tomakehurst.wiremock.client.WireMock.get;
7 | import static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor;
8 | import static com.github.tomakehurst.wiremock.client.WireMock.stubFor;
9 | import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo;
10 | import static com.github.tomakehurst.wiremock.client.WireMock.verify;
11 |
12 | import static junit.framework.TestCase.assertEquals;
13 |
14 | import com.authy.AuthyException;
15 |
16 | import org.junit.Test;
17 |
18 | public class TestPhoneInfo extends TestApiBase {
19 | private static final String successResponse = "{" +
20 | " \"message\": \"Phone number information as of 2017-11-25 23:21:39 UTC\"," +
21 | " \"type\": \"voip\"," +
22 | " \"provider\": \"Pinger\"," +
23 | " \"ported\": false," +
24 | " \"success\": true" +
25 | "}";
26 |
27 | final private String phoneInfoError = "{" +
28 | " \"error_code\": \"60025\"," +
29 | " \"message\": \"Server error while querying phone information. Please try again later\"," +
30 | " \"errors\": {" +
31 | " \"message\": \"Server error while querying phone information. Please try again later\"" +
32 | " }," +
33 | " \"success\": false" +
34 | "}";
35 |
36 | @Test
37 | public void testPhoneInfo() throws AuthyException {
38 | stubFor(get(urlPathEqualTo("/protected/json/phones/info"))
39 | .willReturn(aResponse()
40 | .withStatus(200)
41 | .withHeader("Content-Type", "application/json;charset=utf-8")
42 | .withBody(successResponse)));
43 | final PhoneInfo client = new PhoneInfo(testHost, testApiKey, true);
44 |
45 | final String phoneNumber = "7754615609";
46 | final String countryCode = "1";
47 | final PhoneInfoResponse result = client.info(phoneNumber, countryCode);
48 |
49 | verify(getRequestedFor(urlPathEqualTo("/protected/json/phones/info"))
50 | .withHeader("X-Authy-API-Key", equalTo(testApiKey))
51 | .withQueryParam("phone_number", equalTo(phoneNumber))
52 | .withQueryParam("country_code", equalTo(countryCode)));
53 | assertEquals(true, result.getMessage().contains("Phone number information as of"));
54 | assertEquals("Pinger", result.getProvider());
55 | assertEquals("voip", result.getType());
56 | assertEquals("false", result.getIsPorted());
57 | assertEquals("true", result.getSuccess());
58 | }
59 |
60 | @Test
61 | public void testPhoneInfoError() throws AuthyException {
62 | stubFor(get(urlPathEqualTo("/protected/json/phones/info"))
63 | .willReturn(aResponse()
64 | .withStatus(500)
65 | .withHeader("Content-Type", "application/json")
66 | .withBody(phoneInfoError)));
67 | final PhoneInfo client = new PhoneInfo(testHost, testApiKey, true);
68 |
69 | final String phoneNumber = "7754615609";
70 | final String countryCode = "1";
71 | final PhoneInfoResponse result = client.info(phoneNumber, countryCode);
72 |
73 | assertEquals(true, result.getMessage().contains("Server error while querying phone information"));
74 | assertEquals("false", result.getSuccess());
75 | assertEquals(PHONE_INFO_ERROR_QUERYING, result.getError().getCode());
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/src/test/java/com/authy/api/TestPhoneVerification.java:
--------------------------------------------------------------------------------
1 | package com.authy.api;
2 |
3 | import com.authy.AuthyException;
4 | import org.junit.Before;
5 | import org.junit.Test;
6 |
7 | import static com.authy.api.Error.Code.INVALID_PHONE_NUMBER;
8 | import static com.authy.api.Error.Code.PHONE_VERIFICATION_INCORRECT;
9 | import static com.github.tomakehurst.wiremock.client.WireMock.*;
10 | import static org.junit.Assert.*;
11 |
12 | public class TestPhoneVerification extends TestApiBase {
13 |
14 | private PhoneVerification client;
15 |
16 | private String getStartSuccessResponse(final String message){
17 | return "{" +
18 | " \"carrier\": \"Pinger - Bandwidth.com - Sybase365\"," +
19 | " \"is_cellphone\": false," +
20 | " \"message\": \""+ message + "\"," +
21 | " \"seconds_to_expire\": 599," +
22 | " \"uuid\": \"bec828c0-b535-0135-8e26-1226b57fac04\"," +
23 | " \"success\": true" +
24 | "}";
25 | }
26 |
27 | final private String startInvalidNumberResponse = "{" +
28 | " \"error_code\": \"60033\"," +
29 | " \"message\": \"Phone number is invalid\"," +
30 | " \"errors\": {" +
31 | " \"message\": \"Phone number is invalid\"" +
32 | " }," +
33 | " \"success\": false" +
34 | "}";
35 |
36 | final private String checkIncorrectVerificationResponse = "{" +
37 | " \"error_code\": \"60022\"," +
38 | " \"message\": \"Verification code is incorrect\"," +
39 | " \"errors\": {" +
40 | " \"message\": \"Verification code is incorrect\"" +
41 | " }," +
42 | " \"success\": false" +
43 | "}";
44 |
45 | final private String checkCorrectVerificationResponse = "{" +
46 | " \"message\": \"Verification code is correct.\"," +
47 | " \"success\": true" +
48 | "}";
49 |
50 | @Before
51 | public void setUp() {
52 | client = new PhoneVerification(testHost, testApiKey, true);
53 | }
54 |
55 | @Test
56 | public void testContentTypeToBeJson() {
57 | assertEquals("application/json", client.getContentType());
58 | }
59 |
60 | @Test
61 | public void testVerificationStartEs() throws AuthyException {
62 | stubFor(post(urlPathEqualTo("/protected/json/phones/verification/start"))
63 | .willReturn(aResponse()
64 | .withStatus(200)
65 | .withHeader("Content-Type", "application/json;charset=utf-8")
66 | .withBody(getStartSuccessResponse("Llamada a +1 775-461-5609 fue iniciada."))));
67 |
68 | Params params = new Params();
69 | params.setAttribute("locale", "es");
70 | Verification result = client.start("775-461-5609", "1", "call", params);
71 |
72 | verify(postRequestedFor(urlPathEqualTo("/protected/json/phones/verification/start"))
73 | .withHeader("X-Authy-API-Key", equalTo(testApiKey))
74 | .withRequestBody(equalToJson("{\"country_code\": \"1\", \"phone_number\": \"775-461-5609\", \"locale\": \"es\", \"via\": \"call\"}", true, true)));
75 | assertEquals("Llamada a +1 775-461-5609 fue iniciada.", result.getMessage());
76 | assertEquals("true", result.getSuccess());
77 | }
78 |
79 | @Test
80 | public void testVerificationStartEn() throws AuthyException {
81 | stubFor(post(urlPathEqualTo("/protected/json/phones/verification/start"))
82 | .willReturn(aResponse()
83 | .withStatus(200)
84 | .withHeader("Content-Type", "application/json;charset=utf-8")
85 | .withBody(getStartSuccessResponse("Text message sent to +1 775-461-5609."))));
86 |
87 | Params params = new Params();
88 | params.setAttribute("locale", "en");
89 | Verification result = client.start("775-461-5609", "1", "sms", params);
90 |
91 | verify(postRequestedFor(urlPathEqualTo("/protected/json/phones/verification/start"))
92 | .withHeader("X-Authy-API-Key", equalTo(testApiKey))
93 | .withRequestBody(equalToJson("{\"country_code\": \"1\", \"phone_number\": \"775-461-5609\", \"locale\": \"en\", \"via\": \"sms\"}", true, true)));
94 | String msg = "Text message sent to +1 775-461-5609.";
95 | assertEquals(msg, result.getMessage());
96 | assertEquals("true", result.getSuccess());
97 | }
98 |
99 | @Test
100 | public void testVerificationStartEnInvalid() throws AuthyException {
101 | stubFor(post(urlPathEqualTo("/protected/json/phones/verification/start"))
102 | .willReturn(aResponse()
103 | .withStatus(400)
104 | .withHeader("Content-Type", "application/json")
105 | .withBody(startInvalidNumberResponse)));
106 |
107 | Params params = new Params();
108 | params.setAttribute("locale", "en");
109 |
110 | Verification result = client.start("282-23", "1", "sms", params);
111 |
112 | assertEquals("Phone number is invalid", result.getMessage());
113 | assertEquals("false", result.getSuccess());
114 | Error error = result.getError();
115 | assertNotNull(error);
116 | assertEquals(INVALID_PHONE_NUMBER, error.getCode());
117 | }
118 |
119 | @Test
120 | public void testVerificationCheckSuccess() throws AuthyException {
121 | stubFor(get(urlPathEqualTo("/protected/json/phones/verification/check"))
122 | .willReturn(aResponse()
123 | .withStatus(200)
124 | .withHeader("Content-Type", "application/json")
125 | .withBody(checkCorrectVerificationResponse)));
126 |
127 | Verification result = client.check("775-461-5609", "1", "2061");
128 |
129 | assertEquals("Verification code is correct.", result.getMessage());
130 | assertTrue(result.isOk());
131 | assertEquals("true", result.getSuccess());
132 | }
133 |
134 | @Test
135 | public void testVerificationCheckIncorrectCode() throws AuthyException {
136 | stubFor(get(urlPathEqualTo("/protected/json/phones/verification/check"))
137 | .willReturn(aResponse()
138 | .withStatus(401)
139 | .withHeader("Content-Type", "application/json")
140 | .withBody(checkIncorrectVerificationResponse)));
141 |
142 | Verification result = client.check("775-461-5609", "1", "2061");
143 |
144 | assertEquals("Verification code is incorrect", result.getMessage());
145 | assertFalse(result.isOk());
146 | assertEquals("false", result.getSuccess());
147 | Error error = result.getError();
148 | assertNotNull(error);
149 | assertEquals(PHONE_VERIFICATION_INCORRECT, error.getCode());
150 | }
151 | }
152 |
--------------------------------------------------------------------------------
/src/test/java/com/authy/api/TestUsers.java:
--------------------------------------------------------------------------------
1 | package com.authy.api;
2 |
3 | import com.authy.AuthyApiClient;
4 | import com.authy.AuthyException;
5 |
6 | import org.junit.Before;
7 | import org.junit.Test;
8 |
9 | import java.util.HashMap;
10 |
11 | import static com.authy.api.Error.Code.USER_NOT_FOUND;
12 | import static com.authy.api.Error.Code.USER_NOT_VALID;
13 | import static com.github.tomakehurst.wiremock.client.WireMock.*;
14 |
15 | import static org.hamcrest.CoreMatchers.containsString;
16 | import static org.hamcrest.core.Is.is;
17 | import static org.junit.Assert.*;
18 |
19 | public class TestUsers extends TestApiBase {
20 |
21 | private Users client;
22 | final private String testUserId = "30144611";
23 |
24 | private final String successResponseForced = "{" +
25 | " \"success\": true," +
26 | " \"message\": \"SMS token was sent\"," +
27 | " \"cellphone\": \"+57-XXX-XXX-XX12\"" +
28 | "}";
29 |
30 | private final String userNotFoundResponse = "{" +
31 | " \"message\": \"User not found.\"," +
32 | " \"success\": false," +
33 | " \"errors\": {" +
34 | " \"message\": \"User not found.\"" +
35 | " }," +
36 | " \"error_code\": \"60026\"" +
37 | "}";
38 |
39 | private final String successResponseNotForced = "{" +
40 | " \"message\": \"Ignored: SMS is not needed for smartphones. Pass force=true if you want to actually send it anyway.\"," +
41 | " \"cellphone\": \"+57-XXX-XXX-XX12\"," +
42 | " \"device\": \"android\"," +
43 | " \"ignored\": true," +
44 | " \"success\": true" +
45 | "}";
46 |
47 | private final String successCreateUserResponse = "{\"message\":\"User created successfully.\",\"user\":{\"id\":1000},\"success\":true}";
48 |
49 | @Before
50 | public void setUp() {
51 | client = new AuthyApiClient(testApiKey, testHost, true).getUsers();
52 | }
53 |
54 | @Test
55 | public void testCreateUser() throws AuthyException {
56 | stubFor(post(urlPathEqualTo("/protected/json/users/new"))
57 | .willReturn(aResponse()
58 | .withStatus(200)
59 | .withHeader("Content-Type", "application/json")
60 | .withBody(successCreateUserResponse)));
61 |
62 | final User user = client.createUser("test@example.com", "3003003333", "57");
63 |
64 | verify(postRequestedFor(urlPathEqualTo("/protected/json/users/new"))
65 | .withHeader("X-Authy-API-Key", equalTo(testApiKey))
66 | .withHeader("Content-Type", equalTo("application/json"))
67 | .withRequestBody(equalToJson("{" +
68 | "\"user\": {"
69 | + " \"country_code\" : \"57\","
70 | + " \"cellphone\" : \"3003003333\","
71 | + " \"email\" : \"test@example.com\""
72 | + "}}")));
73 | assertEquals(1000, user.getId());
74 | assertTrue(user.isOk());
75 | }
76 |
77 | @Test
78 | public void testCreateUserDefaultCountry() throws AuthyException {
79 | stubFor(post(urlPathEqualTo("/protected/json/users/new"))
80 | .willReturn(aResponse()
81 | .withStatus(200)
82 | .withHeader("Content-Type", "application/json")
83 | .withBody(successCreateUserResponse)));
84 |
85 | final User user = client.createUser("test@example.com", "3003003333");
86 |
87 | verify(postRequestedFor(urlPathEqualTo("/protected/json/users/new"))
88 | .withHeader("X-Authy-API-Key", equalTo(testApiKey))
89 | .withHeader("Content-Type", equalTo("application/json"))
90 | .withRequestBody(equalToJson("{" +
91 | "\"user\": {"
92 | + " \"country_code\" : \"1\","
93 | + " \"cellphone\" : \"3003003333\","
94 | + " \"email\" : \"test@example.com\""
95 | + "}}")));
96 | assertEquals(1000, user.getId());
97 | assertTrue(user.isOk());
98 | }
99 |
100 | @Test
101 | public void testCreateUserErrorInvalid() throws AuthyException {
102 | stubFor(post(urlPathEqualTo("/protected/json/users/new"))
103 | .willReturn(aResponse()
104 | .withStatus(400)
105 | .withHeader("Content-Type", "application/json")
106 | .withBody("{" +
107 | " \"message\": \"User was not valid\"," +
108 | " \"success\": false," +
109 | " \"errors\": {" +
110 | " \"cellphone\": \"is invalid\"," +
111 | " \"message\": \"User was not valid\"" +
112 | " }," +
113 | " \"cellphone\": \"is invalid\"," +
114 | " \"error_code\": \"60027\"" +
115 | "}")));
116 |
117 | final User user = client.createUser("test@example.com", "3001"); //Invalid Phone sent
118 |
119 | assertFalse(user.isOk());
120 | assertEquals(400, user.getStatus());
121 | final Error error = user.getError();
122 | assertEquals("User was not valid", error.getMessage());
123 | assertEquals(USER_NOT_VALID, error.getCode());
124 | }
125 |
126 | @Test
127 | public void testRequestSMS() throws AuthyException {
128 | stubFor(get(urlPathEqualTo("/protected/json/sms/" + testUserId))
129 | .willReturn(aResponse()
130 | .withStatus(200)
131 | .withHeader("Content-Type", "application/json")
132 | .withBody(successResponseForced)));
133 |
134 | // let's setup some extra parameters
135 | HashMap map = new HashMap<>();
136 | // We are testing SMS here so let's add the force param to have Authy send the SMS even if the
137 | // user has the Authy App installed
138 | map.put("force", "true");
139 | // This is the API normal call you will do to send an SMS, if we don't pass the force option authy will be
140 | // smart enough to decide if it sends the sms or just notifies the user inside the app
141 | Hash response = client.requestSms(Integer.parseInt(testUserId), map);
142 |
143 | verify(getRequestedFor(urlPathEqualTo("/protected/json/sms/" + testUserId))
144 | .withHeader("X-Authy-API-Key", equalTo(testApiKey))
145 | .withQueryParam("force", equalTo("true")));
146 | // isOK() is the method that will allow you to know if the request worked.
147 | assertTrue(response.isOk());
148 | // there's also a response message.
149 | assertEquals("SMS token was sent", response.getMessage());
150 | }
151 |
152 | @Test
153 | public void testUserNotFoundSMS() throws AuthyException {
154 | final Integer badUserId = 0;
155 | stubFor(get(urlPathEqualTo("/protected/json/sms/" + badUserId))
156 | .willReturn(aResponse()
157 | .withStatus(404)
158 | .withHeader("Content-Type", "application/json")
159 | .withBody(userNotFoundResponse)));
160 |
161 | // let's setup some extra parameters
162 | HashMap map = new HashMap<>();
163 | // We are testing SMS here so let's add the force param to have Authy send the SMS even if the
164 | // user has the Authy App installed
165 | map.put("force", "true");
166 | // This is the API normal call you will do to send an SMS, if we don't pass the force option authy will be
167 | // smart enough to decide if it sends the sms or just notifies the user inside the app
168 | Hash reponse = client.requestSms(badUserId, map);
169 |
170 | verify(getRequestedFor(urlPathEqualTo("/protected/json/sms/" + badUserId))
171 | .withHeader("X-Authy-API-Key", equalTo(testApiKey))
172 | .withQueryParam("force", equalTo("true")));
173 | // isOK() is the method that will allow you to know if the request worked.
174 | assertFalse(reponse.isOk());
175 | final Error error = reponse.getError();
176 | assertNotNull(error);
177 | assertEquals(USER_NOT_FOUND, error.getCode());
178 | }
179 |
180 | @Test
181 | public void testRequestSMSNoForce() throws AuthyException {
182 | stubFor(get(urlPathEqualTo("/protected/json/sms/" + testUserId))
183 | .willReturn(aResponse()
184 | .withStatus(200)
185 | .withHeader("Content-Type", "application/json")
186 | .withBody(successResponseNotForced)));
187 |
188 | // This is the API normal call you will do to send an SMS, if we don't pass the force option authy will be
189 | // smart enough to decide if it sends the sms or just notifies the user inside the app
190 | Hash response = client.requestSms(Integer.parseInt(testUserId));
191 |
192 | verify(getRequestedFor(urlPathEqualTo("/protected/json/sms/" + testUserId))
193 | .withHeader("X-Authy-API-Key", equalTo(testApiKey)));
194 | // isOK() is the method that will allow you to know if the request worked.
195 | assertTrue(response.isOk());
196 | // there's also a response message.
197 | assertEquals("Ignored: SMS is not needed for smartphones. Pass force=true if you want to actually send it anyway.", response.getMessage());
198 | }
199 |
200 | @Test
201 | public void testRequestCall() throws AuthyException {
202 | stubFor(get(urlPathEqualTo("/protected/json/call/" + testUserId))
203 | .willReturn(aResponse()
204 | .withStatus(200)
205 | .withHeader("Content-Type", "application/json")
206 | .withBody("{" +
207 | " \"message\": \"Call ignored. User is using App Tokens and this call is not necessary. Pass force=true if you still want to call users that are using the App.\","
208 | +
209 | " \"cellphone\": \"+57-XXX-XXX-XX12\"," +
210 | " \"device\": \"android\"," +
211 | " \"ignored\": true," +
212 | " \"success\": true" +
213 | "}")));
214 |
215 | Hash response = client.requestCall(Integer.parseInt(testUserId));
216 |
217 | verify(getRequestedFor(urlPathEqualTo("/protected/json/call/" + testUserId))
218 | .withHeader("X-Authy-API-Key", equalTo(testApiKey)));
219 | assertTrue(response.isOk());
220 | assertThat(response.getMessage(), containsString("Call ignored. User is using App Tokens and this call is not necessary."));
221 | }
222 |
223 | @Test
224 | public void testRemoveUser() throws AuthyException {
225 | stubFor(post(urlPathEqualTo("/protected/json/users/delete/" + testUserId))
226 | .willReturn(aResponse()
227 | .withStatus(200)
228 | .withHeader("Content-Type", "application/json")
229 | .withBody("{" +
230 | " \"message\": \"User removed from application\"," +
231 | " \"success\": true" +
232 | "}")));
233 |
234 | Hash response = client.deleteUser(Integer.parseInt(testUserId));
235 |
236 | verify(postRequestedFor(urlPathEqualTo("/protected/json/users/delete/" + testUserId))
237 | .withHeader("Content-Type", equalTo("application/json")) //TODO: this Content-Type even if it works doesn't seem like the more appropriate
238 | .withHeader("X-Authy-API-Key", equalTo(testApiKey))
239 | .withRequestBody(equalTo("")));
240 | assertTrue(response.isOk());
241 | assertThat(response.getMessage(), containsString("User removed from application"));
242 | }
243 |
244 | @Test
245 | public void testRemoveUserErrorNotFound() throws AuthyException {
246 | stubFor(post(urlPathEqualTo("/protected/json/users/delete/" + testUserId))
247 | .willReturn(aResponse()
248 | .withStatus(404)
249 | .withHeader("Content-Type", "application/json")
250 | .withBody(userNotFoundResponse)));
251 |
252 | Hash response = client.deleteUser(Integer.parseInt(testUserId));
253 |
254 | assertFalse(response.isOk());
255 | assertThat(response.getStatus(), is(404));
256 | final Error error = response.getError();
257 | assertThat(error.getMessage(), containsString("User not found"));
258 | assertEquals(USER_NOT_FOUND, error.getCode());
259 | }
260 |
261 | @Test
262 | public void testUserToXML() throws Exception {
263 | Users.User user = new Users.User("fake@email.com", "12345678", "1");
264 | String xml = user.toXML();
265 |
266 | assertEquals(xml, "12345678 1 fake@email.com ");
267 | }
268 |
269 | @Test
270 | public void testRequestUserStatus() throws AuthyException {
271 | stubFor(get(urlPathEqualTo("/protected/json/users/" + testUserId + "/status"))
272 | .willReturn(aResponse()
273 | .withStatus(200)
274 | .withHeader("Content-Type", "application/json")
275 | .withBody("{"
276 | + " \"status\": {"
277 | + " \"authy_id\":2,"
278 | + " \"confirmed\":true,"
279 | + " \"registered\":true,"
280 | + " \"country_code\":1,"
281 | + " \"phone_number\":\"XXX-XXX-9302\","
282 | + " \"devices\": ["
283 | + " \"authy_chrome\","
284 | + " \"android\""
285 | + " ]"
286 | + " },"
287 | + " \"message\":\"User status.\","
288 | + " \"success\":true"
289 | + "}")));
290 |
291 | final UserStatus userStatus = client.requestStatus(Integer.parseInt(testUserId));
292 | assertNotNull(userStatus);
293 | assertEquals(2, userStatus.getUserId());
294 | assertEquals("User status.", userStatus.getMessage());
295 | assertEquals(1, userStatus.getCountryCode());
296 | assertEquals("XXX-XXX-9302", userStatus.getPhoneNumber());
297 | assertTrue(userStatus.isConfirmed());
298 | assertTrue(userStatus.isRegistered());
299 | assertEquals(2, userStatus.getDevices().size());
300 | assertEquals("authy_chrome", userStatus.getDevices().get(0));
301 | assertEquals("android", userStatus.getDevices().get(1));
302 | }
303 |
304 | @Test
305 | public void testRequestUserStatusNotFound() throws AuthyException {
306 | stubFor(get(urlPathEqualTo("/protected/json/users/" + testUserId + "/status"))
307 | .willReturn(aResponse()
308 | .withStatus(404)
309 | .withHeader("Content-Type", "application/json")
310 | .withBody(userNotFoundResponse)));
311 |
312 | final UserStatus userStatus = client.requestStatus(Integer.parseInt(testUserId));
313 | assertNotNull(userStatus);
314 | assertFalse(userStatus.isOk());
315 | assertThat(userStatus.getStatus(), is(404));
316 | final Error error = userStatus.getError();
317 | assertThat(error.getMessage(), containsString("User not found"));
318 | assertEquals(USER_NOT_FOUND, error.getCode());
319 |
320 | }
321 | }
322 |
--------------------------------------------------------------------------------
/src/test/java/com/authy/api/TokensTest.java:
--------------------------------------------------------------------------------
1 | package com.authy.api;
2 |
3 | import com.authy.AuthyApiClient;
4 | import com.authy.AuthyException;
5 | import org.junit.Assert;
6 | import org.junit.Before;
7 | import org.junit.Test;
8 | import sun.net.www.protocol.http.HttpURLConnection;
9 |
10 | import java.util.HashMap;
11 | import java.util.Map;
12 |
13 | import static com.authy.api.Error.Code.TOKEN_INVALID;
14 | import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
15 | import static com.github.tomakehurst.wiremock.client.WireMock.equalTo;
16 | import static com.github.tomakehurst.wiremock.client.WireMock.get;
17 | import static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor;
18 | import static com.github.tomakehurst.wiremock.client.WireMock.stubFor;
19 | import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo;
20 | import static com.github.tomakehurst.wiremock.client.WireMock.urlPathMatching;
21 | import static com.github.tomakehurst.wiremock.client.WireMock.verify;
22 | import static junit.framework.TestCase.assertEquals;
23 | import static junit.framework.TestCase.fail;
24 |
25 | public class TokensTest extends TestApiBase {
26 |
27 | private Tokens tokens;
28 | private int testUserId = 123456;
29 | private String testToken = "123456";
30 |
31 | private final String invalidTokenResponse = "{"
32 | + " \"message\": \"Token is invalid\","
33 | + " \"token\": \"is invalid\","
34 | + " \"success\": false,"
35 | + " \"errors\": {"
36 | + " \"message\": \"Token is invalid\""
37 | + " },\n"
38 | + " \"error_code\": \"60020\""
39 | + "}";
40 |
41 | private final String validTokenResponse = "{"
42 | + " \"message\": \"Token is valid.\","
43 | + " \"token\": \"is valid\","
44 | + " \"success\": \"true\","
45 | + " \"device\": {"
46 | + " \"id\": null,"
47 | + " \"os_type\": \"sms\","
48 | + " \"registration_date\": 1500648405,"
49 | + " \"registration_method\": null,"
50 | + " \"registration_country\": null,"
51 | + " \"registration_region\": null,"
52 | + " \"registration_city\": null,"
53 | + " \"country\": null,"
54 | + " \"region\": null,"
55 | + " \"city\": null,"
56 | + " \"ip\": null,"
57 | + " \"last_account_recovery_at\": 1494631010,"
58 | + " \"last_sync_date\": null"
59 | + " }"
60 | + "}";
61 |
62 | private final String invalidErrorFormatResponse = ""
63 | + " \"message\": \"Token is invalid\","
64 | + " \"token\": \"is invalid\","
65 | + " \"success\": false,"
66 | + " \"errors\": {"
67 | + " \"message\": \"Token is invalid\""
68 | + " },"
69 | + " \"error_code\": \"60019\""
70 | + "}";
71 |
72 | @Before
73 | public void setUp() {
74 | tokens = new AuthyApiClient(testApiKey, testHost, true).getTokens();
75 | }
76 |
77 | @Test
78 | public void tokenFormatValidation() {
79 | String alphaToken = "abcde";
80 | try {
81 | tokens.verify(0, alphaToken);
82 | fail("Tokens must be numeric");
83 | } catch (Exception e) {
84 | Assert.assertTrue("Proper exception must be thrown", e instanceof AuthyException);
85 | }
86 | }
87 |
88 | @Test
89 | public void tokenLengthValidation() {
90 | String shortToken = "123";
91 | try {
92 | tokens.verify(0, shortToken);
93 | fail("Tokens must be between 6 and 10 digits");
94 | } catch (Exception e) {
95 | Assert.assertTrue("Proper exception must be thrown", e instanceof AuthyException);
96 | }
97 |
98 | String longToken = "12345678901";
99 | try {
100 | tokens.verify(0, longToken);
101 | fail("Tokens must be between 6 and 10 digits");
102 | } catch (Exception e) {
103 | Assert.assertTrue("Proper exception must be thrown", e instanceof AuthyException);
104 | }
105 | }
106 |
107 | @Test
108 | public void testInvalidTokenResponse() {
109 | stubFor(get(urlPathMatching("/protected/json/verify/.*"))
110 | .willReturn(aResponse()
111 | .withStatus(HttpURLConnection.HTTP_UNAUTHORIZED)
112 | .withHeader("Content-Type", "application/json")
113 | .withBody(invalidTokenResponse)));
114 |
115 |
116 | try {
117 | tokens.verify(testUserId, testToken);
118 | fail("Exception should have been thrown");
119 | } catch (AuthyException e) {
120 | assertEquals(TOKEN_INVALID, e.getErrorCode());
121 | assertEquals(HttpURLConnection.HTTP_UNAUTHORIZED, e.getStatus());
122 | }
123 | }
124 |
125 | @Test
126 | public void testInvalidTokenLength() {
127 | try {
128 | tokens.verify(testUserId, "12345678901234567890");
129 | fail("Exception should have been thrown");
130 | } catch (AuthyException e) {
131 | assertEquals(TOKEN_INVALID, e.getErrorCode());
132 | assertEquals(HttpURLConnection.HTTP_BAD_REQUEST, e.getStatus());
133 | }
134 | }
135 |
136 | @Test
137 | public void testInvalidTokenCharacters() {
138 | stubFor(get(urlPathMatching("/protected/json/verify/.*"))
139 | .willReturn(aResponse()
140 | .withStatus(401)
141 | .withHeader("Content-Type", "application/json")
142 | .withBody(invalidTokenResponse)));
143 |
144 |
145 | try {
146 | tokens.verify(testUserId, "asdasdadasdasdasdasda");
147 | fail("Exception should have been thrown");
148 | } catch (AuthyException e) {
149 | assertEquals(TOKEN_INVALID, e.getErrorCode());
150 | }
151 | }
152 |
153 | @Test
154 | public void testValidTokenResponse() {
155 | stubFor(get(urlPathMatching("/protected/json/verify/.*"))
156 | .willReturn(aResponse()
157 | .withStatus(200)
158 | .withHeader("Content-Type", "application/json")
159 | .withBody(validTokenResponse)));
160 |
161 |
162 | try {
163 | Token token = tokens.verify(testUserId, testToken);
164 | Assert.assertNull("Token must not have an error", token.getError());
165 | Assert.assertTrue("Token verification must be successful", token.isOk());
166 | } catch (AuthyException e) {
167 | fail("Verification should be successful");
168 | }
169 | }
170 |
171 | @Test
172 | public void testVerificationOptions() {
173 | stubFor(get(urlPathMatching("/protected/json/verify/.*"))
174 | .willReturn(aResponse()
175 | .withStatus(200)
176 | .withHeader("Content-Type", "application/json")
177 | .withBody(validTokenResponse)));
178 |
179 |
180 | try {
181 | Map options = new HashMap<>();
182 | options.put("force", "false");
183 | Token token = tokens.verify(testUserId, testToken, options);
184 | Assert.assertNull("Token must not have an error", token.getError());
185 | Assert.assertTrue("Token verification must be successful", token.isOk());
186 | } catch (AuthyException e) {
187 | fail("Verification should be successful");
188 | }
189 | }
190 |
191 | @Test
192 | public void testInvalidErrorFormatResponse() {
193 | stubFor(get(urlPathMatching("/protected/json/verify/.*"))
194 | .willReturn(aResponse()
195 | .withStatus(401)
196 | .withHeader("Content-Type", "application/json")
197 | .withBody(invalidErrorFormatResponse)));
198 |
199 |
200 | try {
201 | tokens.verify(testUserId, testToken);
202 | fail("Exception must be thrown");
203 | } catch (AuthyException e) {
204 | return;
205 | }
206 | fail("Proper exception must be thrown");
207 | }
208 |
209 | @Test
210 | public void testRequestParameters() {
211 | stubFor(get(urlPathMatching("/protected/json/verify/.*"))
212 | .willReturn(aResponse()
213 | .withStatus(200)
214 | .withHeader("Content-Type", "application/json")
215 | .withBody(validTokenResponse)));
216 |
217 |
218 | try {
219 | Token token = tokens.verify(testUserId, testToken);
220 |
221 | verify(getRequestedFor(urlPathEqualTo("/protected/json/verify/" + testToken + "/" + testUserId))
222 | .withHeader("X-Authy-API-Key", equalTo(testApiKey)));
223 | Assert.assertNull("Token must not have an error", token.getError());
224 | Assert.assertTrue("Token verification must be successful", token.isOk());
225 | } catch (AuthyException e) {
226 | fail("Verification should be successful");
227 | }
228 | }
229 | }
--------------------------------------------------------------------------------
/src/test/java/com/authy/api/UserStatusTest.java:
--------------------------------------------------------------------------------
1 | package com.authy.api;
2 |
3 | import org.junit.Before;
4 | import org.junit.Test;
5 |
6 | import java.util.ArrayList;
7 | import java.util.List;
8 | import java.util.Map;
9 |
10 | import static org.junit.Assert.assertEquals;
11 | import static org.junit.Assert.assertNotNull;
12 |
13 | public class UserStatusTest {
14 |
15 | public static final int USER_ID = 1234;
16 | public static final String PHONE_NUMBER = "456 758 8990";
17 | public static final String DEVICE_A = "deviceA";
18 | public static final String DEVICE_B = "deviceB";
19 | private UserStatus userStatus;
20 |
21 | @Before
22 | public void setup() {
23 | userStatus = new UserStatus();
24 | userStatus.setStatus(200);
25 | userStatus.setUserId(USER_ID);
26 | userStatus.setSuccess(true);
27 | userStatus.setConfirmed(true);
28 | userStatus.setRegistered(true);
29 | userStatus.setCountryCode(1);
30 | userStatus.setPhoneNumber(PHONE_NUMBER);
31 | List devices = new ArrayList<>();
32 | devices.add(DEVICE_A);
33 | devices.add(DEVICE_B);
34 | userStatus.setDevices(devices);
35 | }
36 |
37 | @Test
38 | public void testToMap() {
39 | Map userStatusMap = userStatus.toMap();
40 | assertNotNull(userStatusMap);
41 | assertEquals(Integer.toString(USER_ID), userStatusMap.get("userId"));
42 | assertEquals(Boolean.toString(true), userStatusMap.get("success"));
43 | assertEquals(Boolean.toString(true), userStatusMap.get("confirmed"));
44 | assertEquals(Boolean.toString(true), userStatusMap.get("registered"));
45 | assertEquals(Integer.toString(1), userStatusMap.get("countryCode"));
46 | assertEquals(PHONE_NUMBER, userStatusMap.get("phoneNumber"));
47 | assertEquals(String.format("[%s, %s]", DEVICE_A, DEVICE_B), userStatusMap.get("devices"));
48 | }
49 |
50 | @Test
51 | public void testToXML() {
52 | String userStatusXml = userStatus.toXML();
53 | assertNotNull(userStatusXml);
54 | assertEquals("" +
55 | "200 1234 true true " +
56 | "true 1 " +
57 | "deviceA deviceB " +
58 | "456 758 8990 ",
59 | userStatusXml);
60 | }
61 |
62 | @Test
63 | public void testToJSON() {
64 | String userStatusJson = userStatus.toJSON();
65 | assertNotNull(userStatusJson);
66 | assertEquals(userStatusJson, "{\"phoneNumber\":\"456 758 8990\"," +
67 | "\"devices\":\"[deviceA, deviceB]\",\"success\":\"true\"," +
68 | "\"countryCode\":\"1\",\"registered\":\"true\",\"userId\":\"1234\",\"confirmed\":\"true\"}");
69 | }
70 | }
--------------------------------------------------------------------------------
/verify-legacy-v1.md:
--------------------------------------------------------------------------------
1 | # Phone Verification V1
2 |
3 | [Version 2 of the Verify API is now available!](https://www.twilio.com/docs/verify/api) V2 has an improved developer experience and new features. Some of the features of the V2 API include:
4 |
5 | * Twilio helper libraries in JavaScript, Java, C#, Python, Ruby, and PHP
6 | * PSD2 Secure Customer Authentication Support
7 | * Improved Visibility and Insights
8 |
9 | **You are currently viewing Version 1. V1 of the API will be maintained for the time being, but any new features and development will be on Version 2. We strongly encourage you to do any new development with API V2.** Check out the [migration guide](https://www.twilio.com/docs/verify/api/migrating-1x-2x) or the API Reference for more information.
10 |
11 | ### API Reference
12 |
13 | API Reference is available at https://www.twilio.com/docs/verify/api/v1
14 |
15 | ### Sending the verification code.
16 |
17 | ```java
18 | AuthyApiClient client = new AuthyApiClient("SomeApiKey");
19 | PhoneVerification phoneVerification = client.getPhoneVerification();
20 |
21 | Verification verification;
22 | Params params = new Params();
23 | params.setAttribute("locale", en);
24 |
25 | verification = phoneVerification.start("111-111-1111", "1", "sms", params);
26 |
27 | System.out.println(verification.getMessage());
28 | System.out.println(verification.getIsPorted());
29 | System.out.println(verification.getSuccess());
30 | System.out.println(verification.isOk());
31 | ```
32 |
33 | ### Check the verification code.
34 | Once you sent the verification code the user will receive the code in the
35 | mobile device. Then you need to provide this code to check if it is okay.
36 |
37 |
38 | ```java
39 | AuthyApiClient client = new AuthyApiClient("SomeApiKey");
40 | PhoneVerification phoneVerification = client.getPhoneVerification();
41 |
42 | Verification verification;
43 | verification = phoneVerification.check("111-111-1111", "1", "2061");
44 |
45 | System.out.println(verificationCode.getMessage());
46 | System.out.println(verificationCode.getIsPorted());
47 | System.out.println(verificationCode.getSuccess());
48 | ```
--------------------------------------------------------------------------------