Skip to content
This repository was archived by the owner on Dec 12, 2018. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions api/src/main/java/com/stormpath/sdk/oauth/Authenticators.java
Original file line number Diff line number Diff line change
Expand Up @@ -128,5 +128,14 @@ private Authenticators() {
*/
public static final OAuthStormpathSocialRequestAuthenticatorFactory OAUTH_STORMPATH_SOCIAL_GRANT_REQUEST_AUTHENTICATOR =
(OAuthStormpathSocialRequestAuthenticatorFactory) Classes.newInstance("com.stormpath.sdk.impl.oauth.DefaultOAuthStormpathSocialRequestAuthenticatorFactory");

/**
* Constructs {@link OAuthStormpathFactorChallengeGrantRequestAuthenticator}s.
*
* @since 1.3.1
*/
public static final OAuthStormpathFactorChallengeGrantRequestAuthenticatorFactory OAUTH_STORMPATH_FACTOR_CHALLENGE_GRANT_REQUEST_AUTHENTICATOR =
(OAuthStormpathFactorChallengeGrantRequestAuthenticatorFactory) Classes.newInstance("com.stormpath.sdk.impl.oauth.DefaultOAuthStormpathFactorChallengeGrantRequestAuthenticatorFactory");

}

Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* Copyright 2016 Stormpath, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.stormpath.sdk.oauth;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing license header

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added.


/**
* This class represents a request to exchange a multifactor authentication code for a valid OAuth 2.0 access token.
* Using stormpath_factor_challenge grant type
*
* @since 1.3.1
*/
public interface OAuthStormpathFactorChallengeGrantRequestAuthentication extends OAuthGrantRequestAuthentication {

String getChallenge();

String getCode();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* Copyright 2016 Stormpath, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.stormpath.sdk.oauth;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing license header

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added.



/**
* Interface denoting a Stormpath Factor Challenge Grant-specific {@link OAuthRequestAuthenticator}.
* It is used to authenticate an account using a challenge to a factor and receive in exchange
* a valid OAuth 2.0 token.
*
* @since 1.3.1
*/
public interface OAuthStormpathFactorChallengeGrantRequestAuthenticator extends OAuthRequestAuthenticator<OAuthGrantRequestAuthenticationResult> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a short description to this interface, developers will be seeing this interface

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added.


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* Copyright 2016 Stormpath, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.stormpath.sdk.oauth;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing license header

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added.


/**
* A Stormpath Factor Challenge Grant-specific Authenticator Factory.
*
* @since 1.3.1
*/
public interface OAuthStormpathFactorChallengeGrantRequestAuthenticatorFactory extends OAuthRequestAuthenticatorFactory<OAuthStormpathFactorChallengeGrantRequestAuthenticator> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a short description to this interface, developers will be seeing this interface

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added.

}
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,18 @@ import com.stormpath.sdk.application.ApplicationAccountStoreMapping
import com.stormpath.sdk.application.ApplicationAccountStoreMappingList
import com.stormpath.sdk.application.Applications
import com.stormpath.sdk.authc.UsernamePasswordRequests
import com.stormpath.sdk.challenge.google.GoogleAuthenticatorChallenge
import com.stormpath.sdk.challenge.sms.SmsChallenge
import com.stormpath.sdk.client.AuthenticationScheme
import com.stormpath.sdk.client.Client
import com.stormpath.sdk.client.ClientIT
import com.stormpath.sdk.directory.AccountStore
import com.stormpath.sdk.directory.Directories
import com.stormpath.sdk.directory.Directory
import com.stormpath.sdk.factor.FactorOptions
import com.stormpath.sdk.factor.Factors
import com.stormpath.sdk.factor.google.GoogleAuthenticatorFactor
import com.stormpath.sdk.factor.sms.SmsFactor
import com.stormpath.sdk.group.Group
import com.stormpath.sdk.group.Groups
import com.stormpath.sdk.http.HttpMethod
Expand All @@ -47,6 +53,7 @@ import com.stormpath.sdk.impl.ds.DefaultDataStore
import com.stormpath.sdk.impl.error.DefaultError
import com.stormpath.sdk.impl.http.authc.SAuthc1RequestAuthenticator
import com.stormpath.sdk.impl.idsite.IdSiteClaims
import com.stormpath.sdk.impl.oauth.DefaultOAuthStormpathFactorChallengeGrantRequestAuthentication
import com.stormpath.sdk.impl.resource.AbstractResource
import com.stormpath.sdk.impl.saml.SamlResultStatus
import com.stormpath.sdk.impl.security.ApiKeySecretEncryptionService
Expand All @@ -62,6 +69,7 @@ import com.stormpath.sdk.oauth.OAuthPolicy
import com.stormpath.sdk.oauth.OAuthRefreshTokenRequestAuthentication
import com.stormpath.sdk.oauth.OAuthRequestAuthenticator
import com.stormpath.sdk.oauth.OAuthRequests
import com.stormpath.sdk.oauth.OAuthStormpathFactorChallengeGrantRequestAuthentication
import com.stormpath.sdk.oauth.OAuthTokenRevocator
import com.stormpath.sdk.oauth.OAuthTokenRevocators
import com.stormpath.sdk.oauth.RefreshToken
Expand All @@ -82,11 +90,19 @@ import io.jsonwebtoken.Jws
import io.jsonwebtoken.JwsHeader
import io.jsonwebtoken.Jwts
import io.jsonwebtoken.SignatureAlgorithm
import org.apache.commons.codec.binary.Base32
import org.apache.commons.codec.binary.Base64
import org.joda.time.DateTime
import org.joda.time.DateTimeZone
import org.testng.annotations.Test

import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec
import javax.servlet.http.HttpServletRequest
import java.lang.reflect.Field
import java.security.InvalidKeyException
import java.security.NoSuchAlgorithmException
import java.util.concurrent.TimeUnit

import static com.stormpath.sdk.application.Applications.newCreateRequestFor
import static org.easymock.EasyMock.createMock
Expand Down Expand Up @@ -1861,6 +1877,143 @@ class ApplicationIT extends ClientIT {
assertEquals result.getExpiresIn(), 3600
}

/* @since 1.3.1 */
@Test
void testCreateStormpathFactorChallengeTokenForGoogleAuthenticatorFactorWithBadCode() {
def app = createTempApp()

def account = createTestAccount(app)

GoogleAuthenticatorFactor factor = createGoogleAuthenticatorFactor(account)

def challenge = client.instantiate(GoogleAuthenticatorChallenge)
challenge = factor.createChallenge(challenge)

String bogusCode = "000000"
OAuthStormpathFactorChallengeGrantRequestAuthentication request = new DefaultOAuthStormpathFactorChallengeGrantRequestAuthentication(challenge.href, bogusCode)

try {
Authenticators.OAUTH_STORMPATH_FACTOR_CHALLENGE_GRANT_REQUEST_AUTHENTICATOR.forApplication(app).authenticate(request)
fail()
}
catch (ResourceException re) {
assertEquals(re.getStatus(), 400)
assertEquals(re.getCode(), 13104)
}
}

/* @since 1.3.1 */
@Test
void testCreateStormpathFactorChallengeTokenForGoogleAuthenticatorFactorWithValidCode() {
def app = createTempApp()

def account = createTestAccount(app)

GoogleAuthenticatorFactor factor = createGoogleAuthenticatorFactor(account)

sleepToAvoidCrossingThirtySecondMark()

def challenge = client.instantiate(GoogleAuthenticatorChallenge)
challenge = factor.createChallenge(challenge)

String validCode = calculateCurrentTOTP(new Base32().decode(factor.getSecret()))

OAuthStormpathFactorChallengeGrantRequestAuthentication request = new DefaultOAuthStormpathFactorChallengeGrantRequestAuthentication(challenge.href, validCode)

def result = Authenticators.OAUTH_STORMPATH_FACTOR_CHALLENGE_GRANT_REQUEST_AUTHENTICATOR.forApplication(app).authenticate(request)
assertNotNull result.getAccessTokenHref()
assertEquals result.getAccessToken().getHref(), result.getAccessTokenHref()
assertEquals(result.getAccessToken().getAccount().getHref(), account.getHref())
assertEquals(result.getAccessToken().getApplication().getHref(), app.getHref())
assertTrue Strings.hasText(result.getAccessTokenString())

assertNotNull result.getRefreshToken().getHref()
assertEquals(result.getRefreshToken().getAccount().getHref(), account.getHref())
assertEquals(result.getRefreshToken().getApplication().getHref(), app.getHref())

assertEquals result.getTokenType(), "Bearer"
assertEquals result.getExpiresIn(), 3600
}

private GoogleAuthenticatorFactor createGoogleAuthenticatorFactor(Account account) {
GoogleAuthenticatorFactor factor = client.instantiate(GoogleAuthenticatorFactor)
factor = factor.setAccountName("accountName").setIssuer("issuer")

def builder = Factors.GOOGLE_AUTHENTICATOR.newCreateRequestFor(factor).createChallenge()
factor = account.createFactor(builder.build())

FactorOptions factorOptions = Factors.options().withMostRecentChallenge()
factor = client.getResource(factor.href, GoogleAuthenticatorFactor.class, factorOptions)
return factor
}

private static final String HMAC_HASH_FUNCTION = "HmacSHA1";
private static final int KEY_MODULUS = (int) Math.pow(10, CODE_DIGITS);
private static final int CODE_DIGITS = 6;

/**
* Calculates a TOTP from the given key which should agree with the one generated
* by Google Authenticator when provided with the same key.
* See https://en.wikipedia.org/wiki/Time-based_One-time_Password_Algorithm
*
* @param key the key used to compute the TOTP
* @return the current TOTP, as would be computed by Google Authenticator
*/
private static String calculateCurrentTOTP(byte[] key) {
long timeCounter = System.currentTimeMillis() / TimeUnit.SECONDS.toMillis(30)

byte[] data = new byte[8];
long value = timeCounter;

for (int i = 8; i-- > 0; value >>>= 8) {
data[i] = (byte) value;
}

SecretKeySpec signKey = new SecretKeySpec(key, HMAC_HASH_FUNCTION);

try {
Mac mac = Mac.getInstance(HMAC_HASH_FUNCTION);
mac.init(signKey);

byte[] hash = mac.doFinal(data);

int offset = hash[hash.length - 1] & 0xF;

long truncatedHash = 0;
for (int i = 0; i < 4; ++i) {
truncatedHash <<= 8;

// Java bytes are signed but we need an unsigned integer:
// cleaning off all but the LSB.
truncatedHash |= (hash[offset + i] & 0xFF);
}

// Clean bits higher than the 32nd (inclusive) and calculate the
// module with the maximum validation code value.
truncatedHash &= 0x7FFFFFFF;
truncatedHash %= KEY_MODULUS;

return String.format("%06d", (int) truncatedHash)
}
catch (NoSuchAlgorithmException | InvalidKeyException ex) {
throw new IllegalStateException(ex);
}
}

protected void sleepToAvoidCrossingThirtySecondMark() {
DateTime now = new DateTime(DateTimeZone.UTC)
int seconds = now.getSecondOfMinute()
int secondsToWait
if ((seconds <= 30) && (seconds > 25)) {
secondsToWait = 31 - seconds
}
else if ((seconds <= 60) && (seconds > 55)) {
secondsToWait = 61 - seconds
}

sleep(secondsToWait * 1000)
}

/* @since 1.0.RC7 */

@Test
Expand Down Expand Up @@ -2439,4 +2592,5 @@ class ApplicationIT extends ClientIT {

assertFalse result.newAccount
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -15,56 +15,71 @@
*/
package com.stormpath.sdk.servlet.filter.oauth;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.stormpath.sdk.lang.Assert;
import com.stormpath.sdk.lang.Strings;

import java.util.LinkedHashMap;
import java.util.Map;

/**
* @since 1.0.RC3
*/
public class OAuthException extends RuntimeException {

private static final ObjectMapper objectMapper = new ObjectMapper();

private final OAuthErrorCode errorCode;

private Map<String, Object> errorMap;

public OAuthException(OAuthErrorCode code) {
this(code, null, null);
this(code, null, (Exception) null);
}

public OAuthException(OAuthErrorCode code, String message) {
super(message != null ? message : (code != null ? code.getValue() : ""));
Assert.notNull(code, "OAuthErrorCode cannot be null.");
this.errorCode = code;

initializeErrorMap();
}

public OAuthException(OAuthErrorCode code, String message, Exception cause) {
super(message != null ? message : (code != null ? code.getValue() : ""), cause);
Assert.notNull(code, "OAuthErrorCode cannot be null.");
this.errorCode = code;

initializeErrorMap();
}

public OAuthErrorCode getErrorCode() {
return errorCode;
public OAuthException(OAuthErrorCode code, Map<String, Object> error, String message) {
this(code, message, null);

errorMap.putAll(error);
}

public String toJson() {
private void initializeErrorMap() {
errorMap = new LinkedHashMap<>();

String json = "{" + toJson("error", getErrorCode());
errorMap.put("error", errorCode.getValue());

String val = getMessage();
if (Strings.hasText(val)) {
json += "," + toJson("message", val);
errorMap.put("message", val);
}

json += "}";

return json;
}

protected static String toJson(String name, Object value) {
String stringValue = String.valueOf(value);
return quote(name) + ":" + quote(stringValue);
public OAuthErrorCode getErrorCode() {
return errorCode;
}

protected static String quote(String val) {
return "\"" + val + "\"";
public String toJson() {
try {
return objectMapper.writeValueAsString(errorMap);
} catch (Exception e) {
throw new IllegalStateException("Unable to serialize OAuthException to json.", e);
}
}

}
Loading