@@ -32,12 +32,18 @@ import com.stormpath.sdk.application.ApplicationAccountStoreMapping
3232import com.stormpath.sdk.application.ApplicationAccountStoreMappingList
3333import com.stormpath.sdk.application.Applications
3434import com.stormpath.sdk.authc.UsernamePasswordRequests
35+ import com.stormpath.sdk.challenge.google.GoogleAuthenticatorChallenge
36+ import com.stormpath.sdk.challenge.sms.SmsChallenge
3537import com.stormpath.sdk.client.AuthenticationScheme
3638import com.stormpath.sdk.client.Client
3739import com.stormpath.sdk.client.ClientIT
3840import com.stormpath.sdk.directory.AccountStore
3941import com.stormpath.sdk.directory.Directories
4042import com.stormpath.sdk.directory.Directory
43+ import com.stormpath.sdk.factor.FactorOptions
44+ import com.stormpath.sdk.factor.Factors
45+ import com.stormpath.sdk.factor.google.GoogleAuthenticatorFactor
46+ import com.stormpath.sdk.factor.sms.SmsFactor
4147import com.stormpath.sdk.group.Group
4248import com.stormpath.sdk.group.Groups
4349import com.stormpath.sdk.http.HttpMethod
@@ -47,6 +53,7 @@ import com.stormpath.sdk.impl.ds.DefaultDataStore
4753import com.stormpath.sdk.impl.error.DefaultError
4854import com.stormpath.sdk.impl.http.authc.SAuthc1RequestAuthenticator
4955import com.stormpath.sdk.impl.idsite.IdSiteClaims
56+ import com.stormpath.sdk.impl.oauth.DefaultOAuthStormpathFactorChallengeGrantRequestAuthentication
5057import com.stormpath.sdk.impl.resource.AbstractResource
5158import com.stormpath.sdk.impl.saml.SamlResultStatus
5259import com.stormpath.sdk.impl.security.ApiKeySecretEncryptionService
@@ -62,6 +69,7 @@ import com.stormpath.sdk.oauth.OAuthPolicy
6269import com.stormpath.sdk.oauth.OAuthRefreshTokenRequestAuthentication
6370import com.stormpath.sdk.oauth.OAuthRequestAuthenticator
6471import com.stormpath.sdk.oauth.OAuthRequests
72+ import com.stormpath.sdk.oauth.OAuthStormpathFactorChallengeGrantRequestAuthentication
6573import com.stormpath.sdk.oauth.OAuthTokenRevocator
6674import com.stormpath.sdk.oauth.OAuthTokenRevocators
6775import com.stormpath.sdk.oauth.RefreshToken
@@ -82,11 +90,19 @@ import io.jsonwebtoken.Jws
8290import io.jsonwebtoken.JwsHeader
8391import io.jsonwebtoken.Jwts
8492import io.jsonwebtoken.SignatureAlgorithm
93+ import org.apache.commons.codec.binary.Base32
8594import org.apache.commons.codec.binary.Base64
95+ import org.joda.time.DateTime
96+ import org.joda.time.DateTimeZone
8697import org.testng.annotations.Test
8798
99+ import javax.crypto.Mac
100+ import javax.crypto.spec.SecretKeySpec
88101import javax.servlet.http.HttpServletRequest
89102import java.lang.reflect.Field
103+ import java.security.InvalidKeyException
104+ import java.security.NoSuchAlgorithmException
105+ import java.util.concurrent.TimeUnit
90106
91107import static com.stormpath.sdk.application.Applications.newCreateRequestFor
92108import static org.easymock.EasyMock.createMock
@@ -1861,6 +1877,143 @@ class ApplicationIT extends ClientIT {
18611877 assertEquals result. getExpiresIn(), 3600
18621878 }
18631879
1880+ /* @since 1.3.1 */
1881+ @Test
1882+ void testCreateStormpathFactorChallengeTokenForGoogleAuthenticatorFactorWithBadCode () {
1883+ def app = createTempApp()
1884+
1885+ def account = createTestAccount(app)
1886+
1887+ GoogleAuthenticatorFactor factor = createGoogleAuthenticatorFactor(account)
1888+
1889+ def challenge = client. instantiate(GoogleAuthenticatorChallenge )
1890+ challenge = factor. createChallenge(challenge)
1891+
1892+ String bogusCode = " 000000"
1893+ OAuthStormpathFactorChallengeGrantRequestAuthentication request = new DefaultOAuthStormpathFactorChallengeGrantRequestAuthentication (challenge. href, bogusCode)
1894+
1895+ try {
1896+ Authenticators . OAUTH_STORMPATH_FACTOR_CHALLENGE_GRANT_REQUEST_AUTHENTICATOR . forApplication(app). authenticate(request)
1897+ fail ()
1898+ }
1899+ catch (ResourceException re) {
1900+ assertEquals (re. getStatus(), 400 )
1901+ assertEquals (re. getCode(), 13104 )
1902+ }
1903+ }
1904+
1905+ /* @since 1.3.1 */
1906+ @Test
1907+ void testCreateStormpathFactorChallengeTokenForGoogleAuthenticatorFactorWithValidCode () {
1908+ def app = createTempApp()
1909+
1910+ def account = createTestAccount(app)
1911+
1912+ GoogleAuthenticatorFactor factor = createGoogleAuthenticatorFactor(account)
1913+
1914+ sleepToAvoidCrossingThirtySecondMark()
1915+
1916+ def challenge = client. instantiate(GoogleAuthenticatorChallenge )
1917+ challenge = factor. createChallenge(challenge)
1918+
1919+ String validCode = calculateCurrentTOTP(new Base32 (). decode(factor. getSecret()))
1920+
1921+ OAuthStormpathFactorChallengeGrantRequestAuthentication request = new DefaultOAuthStormpathFactorChallengeGrantRequestAuthentication (challenge. href, validCode)
1922+
1923+ def result = Authenticators . OAUTH_STORMPATH_FACTOR_CHALLENGE_GRANT_REQUEST_AUTHENTICATOR . forApplication(app). authenticate(request)
1924+ assertNotNull result. getAccessTokenHref()
1925+ assertEquals result. getAccessToken(). getHref(), result. getAccessTokenHref()
1926+ assertEquals (result. getAccessToken(). getAccount(). getHref(), account. getHref())
1927+ assertEquals (result. getAccessToken(). getApplication(). getHref(), app. getHref())
1928+ assertTrue Strings . hasText(result. getAccessTokenString())
1929+
1930+ assertNotNull result. getRefreshToken(). getHref()
1931+ assertEquals (result. getRefreshToken(). getAccount(). getHref(), account. getHref())
1932+ assertEquals (result. getRefreshToken(). getApplication(). getHref(), app. getHref())
1933+
1934+ assertEquals result. getTokenType(), " Bearer"
1935+ assertEquals result. getExpiresIn(), 3600
1936+ }
1937+
1938+ private GoogleAuthenticatorFactor createGoogleAuthenticatorFactor (Account account ) {
1939+ GoogleAuthenticatorFactor factor = client. instantiate(GoogleAuthenticatorFactor )
1940+ factor = factor. setAccountName(" accountName" ). setIssuer(" issuer" )
1941+
1942+ def builder = Factors . GOOGLE_AUTHENTICATOR . newCreateRequestFor(factor). createChallenge()
1943+ factor = account. createFactor(builder. build())
1944+
1945+ FactorOptions factorOptions = Factors . options(). withMostRecentChallenge()
1946+ factor = client. getResource(factor. href, GoogleAuthenticatorFactor . class, factorOptions)
1947+ return factor
1948+ }
1949+
1950+ private static final String HMAC_HASH_FUNCTION = " HmacSHA1" ;
1951+ private static final int KEY_MODULUS = (int ) Math . pow(10 , CODE_DIGITS );
1952+ private static final int CODE_DIGITS = 6 ;
1953+
1954+ /**
1955+ * Calculates a TOTP from the given key which should agree with the one generated
1956+ * by Google Authenticator when provided with the same key.
1957+ * See https://en.wikipedia.org/wiki/Time-based_One-time_Password_Algorithm
1958+ *
1959+ * @param key the key used to compute the TOTP
1960+ * @return the current TOTP, as would be computed by Google Authenticator
1961+ */
1962+ private static String calculateCurrentTOTP (byte [] key ) {
1963+ long timeCounter = System . currentTimeMillis() / TimeUnit . SECONDS . toMillis(30 )
1964+
1965+ byte [] data = new byte [8 ];
1966+ long value = timeCounter;
1967+
1968+ for (int i = 8 ; i-- > 0 ; value >>>= 8 ) {
1969+ data[i] = (byte ) value;
1970+ }
1971+
1972+ SecretKeySpec signKey = new SecretKeySpec (key, HMAC_HASH_FUNCTION );
1973+
1974+ try {
1975+ Mac mac = Mac . getInstance(HMAC_HASH_FUNCTION );
1976+ mac. init(signKey);
1977+
1978+ byte [] hash = mac. doFinal(data);
1979+
1980+ int offset = hash[hash. length - 1 ] & 0xF ;
1981+
1982+ long truncatedHash = 0 ;
1983+ for (int i = 0 ; i < 4 ; ++ i) {
1984+ truncatedHash << = 8 ;
1985+
1986+ // Java bytes are signed but we need an unsigned integer:
1987+ // cleaning off all but the LSB.
1988+ truncatedHash |= (hash[offset + i] & 0xFF );
1989+ }
1990+
1991+ // Clean bits higher than the 32nd (inclusive) and calculate the
1992+ // module with the maximum validation code value.
1993+ truncatedHash &= 0x7FFFFFFF ;
1994+ truncatedHash % = KEY_MODULUS ;
1995+
1996+ return String . format(" %06d" , (int ) truncatedHash)
1997+ }
1998+ catch (NoSuchAlgorithmException | InvalidKeyException ex) {
1999+ throw new IllegalStateException (ex);
2000+ }
2001+ }
2002+
2003+ protected void sleepToAvoidCrossingThirtySecondMark () {
2004+ DateTime now = new DateTime (DateTimeZone . UTC )
2005+ int seconds = now. getSecondOfMinute()
2006+ int secondsToWait
2007+ if ((seconds <= 30 ) && (seconds > 25 )) {
2008+ secondsToWait = 31 - seconds
2009+ }
2010+ else if ((seconds <= 60 ) && (seconds > 55 )) {
2011+ secondsToWait = 61 - seconds
2012+ }
2013+
2014+ sleep(secondsToWait * 1000 )
2015+ }
2016+
18642017 /* @since 1.0.RC7 */
18652018
18662019 @Test
@@ -2439,4 +2592,5 @@ class ApplicationIT extends ClientIT {
24392592
24402593 assertFalse result. newAccount
24412594 }
2595+
24422596}
0 commit comments