Skip to content

Commit

Permalink
fix persistance of generated state and nonce variables
Browse files Browse the repository at this point in the history
  • Loading branch information
lbalmaceda committed Dec 2, 2016
1 parent 751029f commit 63d3322
Show file tree
Hide file tree
Showing 2 changed files with 100 additions and 31 deletions.
37 changes: 20 additions & 17 deletions auth0/src/main/java/com/auth0/android/provider/WebAuthProvider.java
Original file line number Diff line number Diff line change
Expand Up @@ -395,11 +395,11 @@ private boolean authorize(@NonNull AuthorizeResult data) {
ex = new AuthenticationException("a0.invalid_configuration", "The application isn't configured properly for the social connection. Please check your Auth0's application configuration");
}
callback.onFailure(ex);
} else if (values.containsKey(KEY_STATE) && !values.get(KEY_STATE).equals(getState())) {
Log.e(TAG, String.format("Received state doesn't match. Received %s but expected %s", values.get(KEY_STATE), getState()));
} else if (values.containsKey(KEY_STATE) && !values.get(KEY_STATE).equals(parameters.get(KEY_STATE))) {
Log.e(TAG, String.format("Received state doesn't match. Received %s but expected %s", values.get(KEY_STATE), parameters.get(KEY_STATE)));
final AuthenticationException ex = new AuthenticationException("access_denied", "The received state is invalid. Try again.");
callback.onFailure(ex);
} else if (getResponseType().contains(RESPONSE_TYPE_ID_TOKEN) && !hasValidNonce(getNonce(), values.get(KEY_ID_TOKEN))) {
} else if (getResponseType().contains(RESPONSE_TYPE_ID_TOKEN) && !hasValidNonce(parameters.get(KEY_NONCE), values.get(KEY_ID_TOKEN))) {
Log.e(TAG, "Received nonce doesn't match.");
final AuthenticationException ex = new AuthenticationException("access_denied", "The received nonce is invalid. Try again.");
callback.onFailure(ex);
Expand Down Expand Up @@ -475,11 +475,11 @@ static Credentials mergeCredentials(Credentials urlCredentials, Credentials code
}

@VisibleForTesting
static boolean hasValidNonce(String nonce, String token) {
static boolean hasValidNonce(@Nullable String nonce, @NonNull String token) {
try {
final JWT idToken = new JWT(token);
final Claim nonceClaim = idToken.getClaim(KEY_NONCE);
return nonceClaim != null && nonce.equals(nonceClaim.asString());
return nonce != null && nonceClaim != null && nonce.equals(nonceClaim.asString());
} catch (DecodeException e) {
return false;
}
Expand Down Expand Up @@ -508,12 +508,17 @@ Uri buildAuthorizeUri() {
}

if (getResponseType().contains(RESPONSE_TYPE_ID_TOKEN)) {
queryParameters.put(KEY_NONCE, getNonce());
final String nonce = getRandomString(parameters.get(KEY_NONCE));
parameters.put(KEY_NONCE, nonce);
queryParameters.put(KEY_NONCE, nonce);
}
if (account.getTelemetry() != null) {
queryParameters.put(KEY_TELEMETRY, account.getTelemetry().getValue());
}
queryParameters.put(KEY_STATE, getState());

final String state = getRandomString(parameters.get(KEY_STATE));
parameters.put(KEY_STATE, state);
queryParameters.put(KEY_STATE, state);
queryParameters.put(KEY_CLIENT_ID, account.getClientId());
queryParameters.put(KEY_REDIRECT_URI, redirectUri);

Expand All @@ -522,7 +527,7 @@ Uri buildAuthorizeUri() {
builder.appendQueryParameter(entry.getKey(), entry.getValue());
}
Uri uri = builder.build();
logDebug("The parsed CallbackURI contains the following values: " + "Using the following AuthorizeURI: " + uri.toString());
logDebug("Using the following AuthorizeURI: " + uri.toString());
return uri;
}

Expand All @@ -541,23 +546,21 @@ static WebAuthProvider getInstance() {
return providerInstance;
}


private String getState() {
return parameters.containsKey(KEY_STATE) ? parameters.get(KEY_STATE) : secureRandomString();
@VisibleForTesting
String getRandomString(@Nullable String defaultValue) {
return defaultValue != null ? defaultValue : secureRandomString();
}

String getScope() {
return this.parameters.get(KEY_SCOPE) != null ? this.parameters.get(KEY_SCOPE) : SCOPE_TYPE_OPENID;
@VisibleForTesting
Map<String, String> getParameters() {
return parameters;
}

@VisibleForTesting
boolean isLoggingEnabled() {
return loggingEnabled;
}

private String getNonce() {
return parameters.containsKey(KEY_NONCE) ? parameters.get(KEY_NONCE) : secureRandomString();
}

private String getResponseType() {
return parameters.get(KEY_RESPONSE_TYPE);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
import android.content.Intent;
import android.content.res.Resources;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Base64;

import com.auth0.android.Auth0;
import com.auth0.android.authentication.AuthenticationException;
Expand All @@ -27,6 +29,7 @@
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;

import java.io.UnsupportedEncodingException;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
Expand All @@ -42,6 +45,7 @@
import static android.support.test.espresso.intent.matcher.UriMatchers.hasParamWithValue;
import static android.support.test.espresso.intent.matcher.UriMatchers.hasPath;
import static android.support.test.espresso.intent.matcher.UriMatchers.hasScheme;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.CoreMatchers.notNullValue;
Expand All @@ -64,6 +68,8 @@
public class WebAuthProviderTest {

private static final int REQUEST_CODE = 11;
private static final String KEY_STATE = "state";
private static final String KEY_NONCE = "nonce";

@Mock
private AuthCallback callback;
Expand Down Expand Up @@ -400,7 +406,6 @@ public void shouldSetState() throws Exception {
assertThat(uri, hasParamWithValue("state", "abcdefg"));
}


//nonce

@Test
Expand Down Expand Up @@ -522,6 +527,29 @@ public void shouldSetNonce() throws Exception {
assertThat(uri, hasParamWithValue("nonce", "abcdefg"));
}

@Test
public void shouldGenerateRandomStringIfDefaultValueMissing() throws Exception {
WebAuthProvider.init(account)
.start(activity, callback);
String random1 = WebAuthProvider.getInstance().getRandomString(null);
String random2 = WebAuthProvider.getInstance().getRandomString(null);

assertThat(random1, is(notNullValue()));
assertThat(random2, is(notNullValue()));
assertThat(random1, is(not(equalTo(random2))));
}

@Test
public void shouldNotGenerateRandomStringIfDefaultValuePresent() throws Exception {
WebAuthProvider.init(account)
.start(activity, callback);
String random1 = WebAuthProvider.getInstance().getRandomString("some");
String random2 = WebAuthProvider.getInstance().getRandomString("some");

assertThat(random1, is("some"));
assertThat(random2, is("some"));
}


// auth0 related

Expand Down Expand Up @@ -840,10 +868,14 @@ public void shouldFailToStartWithInvalidAuthorizeURI() throws Exception {
@Test
public void shouldResumeWithRequestCodeWithResponseTypeIdToken() throws Exception {
WebAuthProvider.init(account)
.withNonce("1234567890")
.withResponseType(ResponseType.ID_TOKEN)
.start(activity, callback, REQUEST_CODE);
final Intent intent = createAuthIntent(createHash("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJub25jZSI6IjEyMzQ1Njc4OTAifQ.oUb6xFIEPJQrFbel_Js4SaOwpFfM_kxHxI7xDOHgghk", null, null, null, null, null));

String sentState = WebAuthProvider.getInstance().getParameters().get(KEY_STATE);
String sentNonce = WebAuthProvider.getInstance().getParameters().get(KEY_NONCE);
assertThat(sentState, is(not(isEmptyOrNullString())));
assertThat(sentNonce, is(not(isEmptyOrNullString())));
final Intent intent = createAuthIntent(createHash(customNonceJWT(sentNonce), null, null, null, sentState, null));
assertTrue(WebAuthProvider.resume(REQUEST_CODE, Activity.RESULT_OK, intent));

verify(callback).onSuccess(any(Credentials.class));
Expand All @@ -852,10 +884,15 @@ public void shouldResumeWithRequestCodeWithResponseTypeIdToken() throws Exceptio
@Test
public void shouldResumeWithIntentWithResponseTypeIdToken() throws Exception {
WebAuthProvider.init(account)
.withNonce("1234567890")
.withResponseType(ResponseType.ID_TOKEN)
.start(activity, callback);
final Intent intent = createAuthIntent(createHash("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJub25jZSI6IjEyMzQ1Njc4OTAifQ.oUb6xFIEPJQrFbel_Js4SaOwpFfM_kxHxI7xDOHgghk", null, null, null, null, null));

String sentState = WebAuthProvider.getInstance().getParameters().get(KEY_STATE);

String sentNonce = WebAuthProvider.getInstance().getParameters().get(KEY_NONCE);
assertThat(sentState, is(not(isEmptyOrNullString())));
assertThat(sentNonce, is(not(isEmptyOrNullString())));
final Intent intent = createAuthIntent(createHash(customNonceJWT(sentNonce), null, null, null, sentState, null));
assertTrue(WebAuthProvider.resume(intent));

verify(callback).onSuccess(any(Credentials.class));
Expand All @@ -874,7 +911,6 @@ public Object answer(InvocationOnMock invocation) throws Throwable {
}).when(pkce).getToken(any(String.class), eq(callback));

WebAuthProvider.init(account)
.withState("1234567890")
.useCodeGrant(true)
.withPKCE(pkce)
.start(activity, callback);
Expand All @@ -896,11 +932,14 @@ public Object answer(InvocationOnMock invocation) throws Throwable {
}
}).when(pkce).getToken(any(String.class), codeCallbackCaptor.capture());
WebAuthProvider.init(account)
.withState("1234567890")
.useCodeGrant(true)
.withPKCE(pkce)
.start(activity, callback);
final Intent intent = createAuthIntent(createHash("urlId", "urlAccess", "urlRefresh", "urlType", "1234567890", null));

String sentState = WebAuthProvider.getInstance().getParameters().get(KEY_STATE);

assertThat(sentState, is(not(isEmptyOrNullString())));
final Intent intent = createAuthIntent(createHash("urlId", "urlAccess", "urlRefresh", "urlType", sentState, null));
assertTrue(WebAuthProvider.resume(intent));

final ArgumentCaptor<Credentials> credentialsCaptor = ArgumentCaptor.forClass(Credentials.class);
Expand All @@ -926,11 +965,14 @@ public Object answer(InvocationOnMock invocation) throws Throwable {
}
}).when(pkce).getToken(any(String.class), codeCallbackCaptor.capture());
WebAuthProvider.init(account)
.withState("1234567890")
.useCodeGrant(true)
.withPKCE(pkce)
.start(activity, callback, REQUEST_CODE);
final Intent intent = createAuthIntent(createHash("urlId", "urlAccess", "urlRefresh", "urlType", "1234567890", null));

String sentState = WebAuthProvider.getInstance().getParameters().get(KEY_STATE);

assertThat(sentState, is(not(isEmptyOrNullString())));
final Intent intent = createAuthIntent(createHash("urlId", "urlAccess", "urlRefresh", "urlType", sentState, null));
assertTrue(WebAuthProvider.resume(REQUEST_CODE, Activity.RESULT_OK, intent));

final ArgumentCaptor<Credentials> credentialsCaptor = ArgumentCaptor.forClass(Credentials.class);
Expand All @@ -946,10 +988,13 @@ public Object answer(InvocationOnMock invocation) throws Throwable {
@Test
public void shouldResumeWithIntentWithImplicitGrant() throws Exception {
WebAuthProvider.init(account)
.withState("1234567890")
.useCodeGrant(false)
.start(activity, callback);
final Intent intent = createAuthIntent(createHash("iToken", "aToken", null, "refresh_token", "1234567890", null));

String sentState = WebAuthProvider.getInstance().getParameters().get(KEY_STATE);

assertThat(sentState, is(not(isEmptyOrNullString())));
final Intent intent = createAuthIntent(createHash("urlId", "urlAccess", "urlRefresh", "urlType", sentState, null));
assertTrue(WebAuthProvider.resume(intent));

verify(callback).onSuccess(any(Credentials.class));
Expand All @@ -958,10 +1003,13 @@ public void shouldResumeWithIntentWithImplicitGrant() throws Exception {
@Test
public void shouldResumeWithRequestCodeWithImplicitGrant() throws Exception {
WebAuthProvider.init(account)
.withState("1234567890")
.useCodeGrant(false)
.start(activity, callback, REQUEST_CODE);
final Intent intent = createAuthIntent(createHash("iToken", "aToken", null, "refresh_token", "1234567890", null));

String sentState = WebAuthProvider.getInstance().getParameters().get(KEY_STATE);

assertThat(sentState, is(not(isEmptyOrNullString())));
final Intent intent = createAuthIntent(createHash("urlId", "urlAccess", "urlRefresh", "urlType", sentState, null));
assertTrue(WebAuthProvider.resume(REQUEST_CODE, Activity.RESULT_OK, intent));

verify(callback).onSuccess(any(Credentials.class));
Expand Down Expand Up @@ -1334,4 +1382,22 @@ private String createHash(@Nullable String idToken, @Nullable String accessToken
}
return hash.length() == 1 ? "" : hash;
}

private String customNonceJWT(@NonNull String nonce) {
String header = encodeString("{}");
String bodyBuilder = "{\"nonce\":\"" + nonce + "\"}";
String body = encodeString(bodyBuilder);
String signature = "sign";
return String.format("%s.%s.%s", header, body, signature);
}

private String encodeString(String source) {
byte[] bytes = Base64.encode(source.getBytes(), Base64.URL_SAFE | Base64.NO_WRAP | Base64.NO_PADDING);
String res = "";
try {
res = new String(bytes, "UTF-8");
} catch (UnsupportedEncodingException ignored) {
}
return res;
}
}

0 comments on commit 63d3322

Please sign in to comment.