Skip to content
This repository was archived by the owner on Dec 12, 2018. It is now read-only.

Commit a239274

Browse files
authored
Merge pull request #1244 from stormpath/GA_Refactor
Refactor GoogleAuthenticatorChallenge to allow for setting code along with challenge.
2 parents 0cc957f + 5e88bfe commit a239274

12 files changed

+214
-23
lines changed

api/src/main/java/com/stormpath/sdk/challenge/google/GoogleAuthenticatorChallenge.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,13 @@
2929
*
3030
* @since 1.1.0
3131
*/
32-
public interface GoogleAuthenticatorChallenge extends Challenge<GoogleAuthenticatorFactor,GoogleAuthenticatorChallengeStatus>{
32+
public interface GoogleAuthenticatorChallenge extends Challenge<GoogleAuthenticatorFactor, GoogleAuthenticatorChallengeStatus> {
3333

34+
/**
35+
* A GoogleAuthenticatorChallenge can be validated at the same time the challenge is created by setting the code on the challenge.
36+
*
37+
* @param code the code the validated at the same time the GoogleAuthenticatorChallenge is created
38+
* @since 1.4.0
39+
*/
40+
void setCode(String code);
3441
}

api/src/main/java/com/stormpath/sdk/challenge/google/GoogleAuthenticatorChallenges.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ public final class GoogleAuthenticatorChallenges extends Challenges {
5858
}
5959

6060
private static final Class<CreateChallengeRequestBuilder> BUILDER_CLASS =
61-
Classes.forName("com.stormpath.sdk.impl.challenge.DefaultCreateChallengeRequestBuilder");
61+
Classes.forName("com.stormpath.sdk.impl.challenge.google.DefaultGoogleAuthenticatorCreateChallengeRequestBuilder");
6262

6363
//Prevent instantiation outside of outer class.
6464
//Use getInstance() to retrieve the singleton instance.
@@ -152,9 +152,9 @@ public static EqualsExpressionFactory status() {
152152
*
153153
* @since 1.1.0
154154
*/
155-
public static CreateChallengeRequestBuilder<GoogleAuthenticatorChallenge> newCreateRequestFor(GoogleAuthenticatorChallenge challenge) {
156-
Constructor ctor = Classes.getConstructor(BUILDER_CLASS, Challenge.class);
157-
return (CreateChallengeRequestBuilder<GoogleAuthenticatorChallenge>) Classes.instantiate(ctor, challenge);
155+
public static GoogleAuthenticatorCreateChallengeRequestBuilder newCreateRequestFor(GoogleAuthenticatorChallenge challenge) {
156+
Constructor ctor = Classes.getConstructor(BUILDER_CLASS, GoogleAuthenticatorChallenge.class);
157+
return (GoogleAuthenticatorCreateChallengeRequestBuilder) Classes.instantiate(ctor, challenge);
158158
}
159159

160160
private static EqualsExpressionFactory newEqualsExpressionFactory(String propName) {
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*
2+
* Copyright 2017 Stormpath, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.stormpath.sdk.challenge.google;
17+
18+
import com.stormpath.sdk.challenge.CreateChallengeRequest;
19+
20+
/**
21+
* A Google Authenticator specific create challenge request
22+
*
23+
* Google Authenticator challenges can validate a code at the same time that the challenge is created.
24+
*
25+
* @since 1.4.0
26+
*/
27+
public interface GoogleAuthenticatorCreateChallengeRequest extends CreateChallengeRequest {}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
* Copyright 2017 Stormpath, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.stormpath.sdk.challenge.google;
17+
18+
import com.stormpath.sdk.challenge.CreateChallengeRequestBuilder;
19+
20+
/**
21+
* A builder to construct {@link GoogleAuthenticatorCreateChallengeRequest}s Google Authenticator specific create challenge requests.
22+
*
23+
* Google Authenticator can both create the challenge and set the code in one API call.
24+
* See {@link com.stormpath.sdk.factor.google.GoogleAuthenticatorFactor}
25+
*
26+
* @since 1.4.0
27+
*/
28+
public interface GoogleAuthenticatorCreateChallengeRequestBuilder extends CreateChallengeRequestBuilder<GoogleAuthenticatorChallenge> {
29+
30+
/**
31+
* @param code to be used to validate the challenge for the {@link com.stormpath.sdk.factor.google.GoogleAuthenticatorFactor}
32+
* @return GoogleAuthenticatorCreateChallengeRequestBuilder for method chaining with the builder pattern
33+
*/
34+
GoogleAuthenticatorCreateChallengeRequestBuilder withCode(String code);
35+
}

api/src/main/java/com/stormpath/sdk/factor/google/GoogleAuthenticatorFactor.java

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,16 @@
2222
* A {@code GoogleAuthenticatorFactor} is a Factor that represents a two-step verification services using
2323
* the Time-based One-time Password Algorithm (TOTP) and HMAC-based One-time Password Algorithm (HOTP),
2424
* for authenticating users of mobile applications by Google.
25-
* When issuing a challenge via an GoogleAuthenticatorFactor, a code is sent to the Google Authenticator app,
26-
* and the user can enter the received code back into the system to verify/complete the challenge.
25+
* <p/>
26+
* The TOTP algorithm determines the next valid code without the requirement for any communication between server and
27+
* TOTP client.
28+
* <p/>
29+
* As such, the challenge can be created with the code at the same time:
30+
* <p/>
31+
*
32+
* <pre>
33+
* GoogleAuthenticatorChallenge challenge = googleAuthenticatorFactor.createChallenge(code);
34+
* </pre>
2735
*
2836
* @since 1.1.0
2937
*/
@@ -80,4 +88,13 @@ public interface GoogleAuthenticatorFactor<T extends GoogleAuthenticatorChalleng
8088
* @return the base64QRImage.
8189
*/
8290
String getBase64QrImage();
91+
92+
/**
93+
*
94+
* @param code The code to validate the challenge that is created. With Google Authenticator, you can create a
95+
* challenge and validate it all in one call.
96+
* @return the {@link GoogleAuthenticatorChallenge}
97+
* @since 1.4.0
98+
*/
99+
GoogleAuthenticatorChallenge createChallenge(String code);
83100
}

extensions/httpclient/src/test/groovy/com/stormpath/sdk/impl/challenge/GoogleAuthenticatorChallengeIT.groovy

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ import static org.testng.Assert.assertNotNull
4343
class GoogleAuthenticatorChallengeIT extends AbstractMultiFactorIT {
4444

4545
@Test
46-
void testSuccessfulGoogleAuthenticatorChallenge() {
46+
void testSuccessfulGoogleAuthenticatorChallengeWithCode() {
4747
Directory dir = client.instantiate(Directory)
4848
dir.name = uniquify("Java SDK: ${this.getClass().getSimpleName()}.${new Object(){}.getClass().getEnclosingMethod().getName()}")
4949
dir = client.currentTenant.createDirectory(dir);
@@ -60,6 +60,24 @@ class GoogleAuthenticatorChallengeIT extends AbstractMultiFactorIT {
6060
assertGoogleAuthenticatorChallengeResponse(factor, getCurrentValidCode(factor), 'SUCCESS')
6161
}
6262

63+
@Test
64+
void testSuccessfulGoogleAuthenticatorChallengeTwoStep() {
65+
Directory dir = client.instantiate(Directory)
66+
dir.name = uniquify("Java SDK: ${this.getClass().getSimpleName()}.${new Object(){}.getClass().getEnclosingMethod().getName()}")
67+
dir = client.currentTenant.createDirectory(dir);
68+
deleteOnTeardown(dir)
69+
Account account = createTempAccountInDir(dir)
70+
71+
def randomAccountName = uniquify("Random Account Name")
72+
def randomIssuer = uniquify("Random Issuer")
73+
def factor = createGoogleAuthenticatorFactor(account, randomIssuer, randomAccountName)
74+
assertGoogleAuthenticatorFactorFields(factor, randomIssuer, randomAccountName)
75+
76+
sleepToAvoidCrossingThirtySecondMark()
77+
78+
GoogleAuthenticatorChallenge challenge = createChallengeWithCode(factor)
79+
assertTrue challenge.validate(getCurrentValidCode(factor))
80+
}
6381

6482
@Test
6583
void testGoogleAuthenticatorChallengeWithGarbageCode() {
@@ -75,7 +93,7 @@ class GoogleAuthenticatorChallengeIT extends AbstractMultiFactorIT {
7593

7694
Throwable e = null
7795
try {
78-
createChallenge(factor, "bogus")
96+
createChallengeWithCode(factor, "bogus")
7997
} catch (ResourceException re) {
8098
e = re
8199
assertEquals(re.status, 400)
@@ -98,7 +116,7 @@ class GoogleAuthenticatorChallengeIT extends AbstractMultiFactorIT {
98116

99117
Throwable e = null
100118
try {
101-
createChallenge(factor, "123456")
119+
createChallengeWithCode(factor, "123456")
102120
} catch (ResourceException re) {
103121
e = re
104122
assertEquals(re.status, 400)
@@ -127,7 +145,7 @@ class GoogleAuthenticatorChallengeIT extends AbstractMultiFactorIT {
127145

128146
private void assertGoogleAuthenticatorChallengeResponse(GoogleAuthenticatorFactor factor, String code, String status, String verificationStatus = 'VERIFIED') {
129147
String factorHref = factor.href
130-
GoogleAuthenticatorChallenge challenge = createChallenge(factor, code)
148+
GoogleAuthenticatorChallenge challenge = createChallengeWithCode(factor, code)
131149
assertInitialChallengeFields(challenge, status, false)
132150

133151
FactorOptions options = Factors.options().withChallenges().withMostRecentChallenge()
@@ -182,13 +200,19 @@ class GoogleAuthenticatorChallengeIT extends AbstractMultiFactorIT {
182200
sleep(secondsToWait * 1000)
183201
}
184202

185-
private GoogleAuthenticatorChallenge createChallenge(GoogleAuthenticatorFactor factor, String code = null) {
203+
private GoogleAuthenticatorChallenge createChallengeWithCode(GoogleAuthenticatorFactor factor, String code = null) {
186204
def challenge = client.instantiate(GoogleAuthenticatorChallenge.class)
187205
challenge.setCode(code)
188206
ChallengeOptions options = Challenges.GOOGLE_AUTHENTICATOR.options().withFactor()
189207
return factor.createChallenge(Challenges.GOOGLE_AUTHENTICATOR.newCreateRequestFor(challenge).withResponseOptions(options).build())
190208
}
191209

210+
private GoogleAuthenticatorChallenge createChallengeWithoutCode(GoogleAuthenticatorFactor factor) {
211+
def challenge = client.instantiate(GoogleAuthenticatorChallenge.class)
212+
ChallengeOptions options = Challenges.GOOGLE_AUTHENTICATOR.options().withFactor()
213+
return factor.createChallenge(Challenges.GOOGLE_AUTHENTICATOR.newCreateRequestFor(challenge).withResponseOptions(options).build())
214+
}
215+
192216
private String getCurrentValidCode(GoogleAuthenticatorFactor factor) {
193217
return getCurrentCode(factor, true)
194218
}

impl/src/main/java/com/stormpath/sdk/impl/challenge/AbstractChallenge.java

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
public abstract class AbstractChallenge<T extends Factor, R extends Enum> extends AbstractInstanceResource implements Challenge<T,R> {
3232

3333
public static final EnumProperty<Enum> STATUS = new EnumProperty<>("status", Enum.class);
34-
static final StringProperty CODE = new StringProperty("code");
34+
public static final StringProperty CODE = new StringProperty("code");
3535
static final ResourceReference<Account> ACCOUNT = new ResourceReference<>("account", Account.class);
3636
static final ResourceReference<? extends Factor> FACTOR = new ResourceReference<>("factor", Factor.class);
3737
public static final DateProperty CREATED_AT = new DateProperty("createdAt");
@@ -92,16 +92,11 @@ public Challenge setFactor(T factor) {
9292
@Override
9393
public boolean validate(String code) {
9494
Assert.notNull(code, "code can not be null.");
95-
setCode(code);
95+
setProperty(CODE, code);
9696
Challenge returnedChallenge = getDataStore().create(getHref(), this);
9797
if ((returnedChallenge.getStatus()).name().equals("SUCCESS")) {
9898
return true;
9999
}
100100
return false;
101101
}
102-
103-
protected Challenge setCode(String code) {
104-
setProperty(CODE, code);
105-
return this;
106-
}
107102
}

impl/src/main/java/com/stormpath/sdk/impl/challenge/DefaultCreateChallengeRequestBuilder.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,10 @@
2424
/**
2525
* @since 1.1.0
2626
*/
27-
public class DefaultCreateChallengeRequestBuilder<T extends Challenge> implements CreateChallengeRequestBuilder {
27+
public class DefaultCreateChallengeRequestBuilder<T extends Challenge> implements CreateChallengeRequestBuilder<T> {
2828

29-
private final T challenge;
30-
private ChallengeOptions options;
29+
protected final T challenge;
30+
protected ChallengeOptions options;
3131

3232
public DefaultCreateChallengeRequestBuilder(T challenge) {
3333
Assert.notNull(challenge, "Challenge can't be null.");

impl/src/main/java/com/stormpath/sdk/impl/challenge/google/DefaultGoogleAuthenticatorChallenge.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
/**
2828
* @since 1.1.0
2929
*/
30-
public class DefaultGoogleAuthenticatorChallenge extends AbstractChallenge<GoogleAuthenticatorFactor, GoogleAuthenticatorChallengeStatus> implements GoogleAuthenticatorChallenge{
30+
public class DefaultGoogleAuthenticatorChallenge extends AbstractChallenge<GoogleAuthenticatorFactor, GoogleAuthenticatorChallengeStatus> implements GoogleAuthenticatorChallenge {
3131

3232
static final Map<String, Property> PROPERTY_DESCRIPTORS = AbstractChallenge.PROPERTY_DESCRIPTORS;
3333

@@ -53,4 +53,9 @@ public GoogleAuthenticatorChallengeStatus getStatus() {
5353
}
5454
return GoogleAuthenticatorChallengeStatus.valueOf(value.toUpperCase());
5555
}
56+
57+
@Override
58+
public void setCode(String code) {
59+
setProperty(CODE, code);
60+
}
5661
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*
2+
* Copyright 2017 Stormpath, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.stormpath.sdk.impl.challenge.google;
17+
18+
import com.stormpath.sdk.challenge.Challenge;
19+
import com.stormpath.sdk.challenge.ChallengeOptions;
20+
import com.stormpath.sdk.challenge.google.GoogleAuthenticatorCreateChallengeRequest;
21+
import com.stormpath.sdk.impl.challenge.DefaultCreateChallengeRequest;
22+
23+
/**
24+
* @since 1.4.0
25+
*/
26+
public class DefaultGoogleAuthenticatorCreateChallengeRequest extends DefaultCreateChallengeRequest implements GoogleAuthenticatorCreateChallengeRequest {
27+
28+
public DefaultGoogleAuthenticatorCreateChallengeRequest(Challenge challenge, ChallengeOptions options) {
29+
super(challenge, options);
30+
}
31+
}

0 commit comments

Comments
 (0)