Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Store RegistrationResponse in AuthState. #61

Merged
merged 1 commit into from
May 4, 2016
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
9 changes: 0 additions & 9 deletions app/src/net/openid/appauthdemo/IdentityProvider.java
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,6 @@ public static List<IdentityProvider> getEnabledProviders(Context context) {
private Uri mTokenEndpoint;
private Uri mRegistrationEndpoint;
private String mClientId;
private String mClientSecret;
private Uri mRedirectUri;
private String mScope;

Expand Down Expand Up @@ -215,19 +214,11 @@ public String getClientId() {
return mClientId;
}

@Nullable
public String getClientSecret() {
return mClientSecret;
}

public void setClientId(String clientId) {
mClientId = clientId;
}

public void setClientSecret(String clientSecret) {
mClientSecret = clientSecret;
}

@NonNull
public Uri getRedirectUri() {
checkConfigurationRead();
Expand Down
12 changes: 7 additions & 5 deletions app/src/net/openid/appauthdemo/MainActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import android.widget.LinearLayout;
import android.widget.TextView;

import net.openid.appauth.AuthState;
import net.openid.appauth.AuthorizationException;
import net.openid.appauth.AuthorizationRequest;
import net.openid.appauth.AuthorizationService;
Expand Down Expand Up @@ -85,7 +86,7 @@ public void onFetchConfigurationCompleted(
// Do dynamic client registration if no client_id
makeRegistrationRequest(serviceConfiguration, idp);
} else {
makeAuthRequest(serviceConfiguration, idp);
makeAuthRequest(serviceConfiguration, idp, new AuthState());
}
}
}
Expand Down Expand Up @@ -127,7 +128,8 @@ protected void onDestroy() {

private void makeAuthRequest(
@NonNull AuthorizationServiceConfiguration serviceConfig,
@NonNull IdentityProvider idp) {
@NonNull IdentityProvider idp,
@NonNull AuthState authState) {

AuthorizationRequest authRequest = new AuthorizationRequest.Builder(
serviceConfig,
Expand All @@ -144,7 +146,7 @@ private void makeAuthRequest(
this,
authRequest,
serviceConfig.discoveryDoc,
idp.getClientSecret()),
authState),
mAuthService.createCustomTabsIntentBuilder()
.setToolbarColor(getColorCompat(R.color.colorAccent))
.build());
Expand All @@ -171,10 +173,10 @@ public void onRegistrationRequestCompleted(
Log.d(TAG, "Registration request complete");
if (registrationResponse != null) {
idp.setClientId(registrationResponse.clientId);
idp.setClientSecret(registrationResponse.clientSecret);
Log.d(TAG, "Registration request complete successfully");
// Continue with the authentication
makeAuthRequest(registrationResponse.request.configuration, idp);
makeAuthRequest(registrationResponse.request.configuration, idp,
new AuthState((registrationResponse)));
}
}
});
Expand Down
55 changes: 26 additions & 29 deletions app/src/net/openid/appauthdemo/TokenActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,6 @@
import net.openid.appauth.AuthorizationService;
import net.openid.appauth.AuthorizationServiceDiscovery;
import net.openid.appauth.ClientAuthentication;
import net.openid.appauth.ClientSecretBasic;
import net.openid.appauth.NoClientAuthentication;
import net.openid.appauth.TokenRequest;
import net.openid.appauth.TokenResponse;

Expand All @@ -75,7 +73,7 @@ public class TokenActivity extends AppCompatActivity {
private static final String KEY_USER_INFO = "userInfo";

private static final String EXTRA_AUTH_SERVICE_DISCOVERY = "authServiceDiscovery";
private static final String EXTRA_CLIENT_SECRET = "clientSecret";
private static final String EXTRA_AUTH_STATE = "authState";

private static final int BUFFER_SIZE = 1024;

Expand Down Expand Up @@ -110,19 +108,15 @@ protected void onCreate(Bundle savedInstanceState) {
}

if (mAuthState == null) {
mAuthState = getAuthStateFromIntent(getIntent());
AuthorizationResponse response = AuthorizationResponse.fromIntent(getIntent());
AuthorizationException ex = AuthorizationException.fromIntent(getIntent());
mAuthState = new AuthState(response, ex);
mAuthState.update(response, ex);

if (response != null) {
Log.d(TAG, "Received AuthorizationResponse.");
showSnackbar(R.string.exchange_notification);
String clientSecret = getClientSecretFromIntent(getIntent());
if (clientSecret != null) {
exchangeAuthorizationCode(response, new ClientSecretBasic(clientSecret));
} else {
exchangeAuthorizationCode(response);
}
exchangeAuthorizationCode(response);
} else {
Log.i(TAG, "Authorization failed: " + ex);
showSnackbar(R.string.authorization_failed);
Expand Down Expand Up @@ -261,19 +255,23 @@ private void refreshAccessToken() {
performTokenRequest(mAuthState.createTokenRefreshRequest());
}

private void exchangeAuthorizationCode(AuthorizationResponse authorizationResponse,
ClientAuthentication clientAuth) {
performTokenRequest(authorizationResponse.createTokenExchangeRequest(), clientAuth);
}

private void exchangeAuthorizationCode(AuthorizationResponse authorizationResponse) {
performTokenRequest(authorizationResponse.createTokenExchangeRequest());
}

private void performTokenRequest(TokenRequest request, ClientAuthentication clientAuth) {
private void performTokenRequest(TokenRequest request) {
ClientAuthentication clientAuthentication = null;
try {
clientAuthentication = mAuthState.getClientAuthentication();
} catch (ClientAuthentication.UnsupportedAuthenticationMethod ex) {
Log.d(TAG, "Token request cannot be made, client authentication for the token "
+ "endpoint could not be constructed (%s)", ex);
return;
}

mAuthService.performTokenRequest(
request,
clientAuth,
clientAuthentication,
new AuthorizationService.TokenResponseCallback() {
@Override
public void onTokenRequestCompleted(
Expand All @@ -284,10 +282,6 @@ public void onTokenRequestCompleted(
});
}

private void performTokenRequest(TokenRequest request) {
performTokenRequest(request, NoClientAuthentication.INSTANCE);
}

private void fetchUserInfo() {
if (mAuthState.getAuthorizationServiceConfiguration() == null) {
Log.e(TAG, "Cannot make userInfo request without service configuration");
Expand Down Expand Up @@ -372,14 +366,12 @@ static PendingIntent createPostAuthorizationIntent(
@NonNull Context context,
@NonNull AuthorizationRequest request,
@Nullable AuthorizationServiceDiscovery discoveryDoc,
@Nullable String clientSecret) {
@NonNull AuthState authState) {
Intent intent = new Intent(context, TokenActivity.class);
intent.putExtra(EXTRA_AUTH_STATE, authState.jsonSerializeString());
if (discoveryDoc != null) {
intent.putExtra(EXTRA_AUTH_SERVICE_DISCOVERY, discoveryDoc.docJson.toString());
}
if (clientSecret != null) {
intent.putExtra(EXTRA_CLIENT_SECRET, clientSecret);
}

return PendingIntent.getActivity(context, request.hashCode(), intent, 0);
}
Expand All @@ -396,11 +388,16 @@ static AuthorizationServiceDiscovery getDiscoveryDocFromIntent(Intent intent) {
}
}

static String getClientSecretFromIntent(Intent intent) {
if (!intent.hasExtra(EXTRA_CLIENT_SECRET)) {
return null;
static AuthState getAuthStateFromIntent(Intent intent) {
if (!intent.hasExtra(EXTRA_AUTH_STATE)) {
throw new IllegalArgumentException("The AuthState instance is missing in the intent.");
}
try {
return AuthState.jsonDeserialize(intent.getStringExtra(EXTRA_AUTH_STATE));
} catch (JSONException ex) {
Log.e(TAG, "Malformed AuthState JSON saved", ex);
throw new IllegalArgumentException("The AuthState instance is missing in the intent.");
}
return intent.getStringExtra(EXTRA_CLIENT_SECRET);
}

private class UserProfilePictureTarget implements Target {
Expand Down
123 changes: 123 additions & 0 deletions library/java/net/openid/appauth/AuthState.java
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ public class AuthState {
private static final String KEY_LAST_AUTHORIZATION_RESPONSE = "lastAuthorizationResponse";
private static final String KEY_LAST_TOKEN_RESPONSE = "mLastTokenResponse";
private static final String KEY_AUTHORIZATION_EXCEPTION = "mAuthorizationException";
private static final String KEY_LAST_REGISTRATION_RESPONSE = "lastRegistrationResponse";

@Nullable
private String mRefreshToken;
Expand All @@ -63,6 +64,9 @@ public class AuthState {
@Nullable
private TokenResponse mLastTokenResponse;

@Nullable
private RegistrationResponse mLastRegistrationResponse;

@Nullable
private AuthorizationException mAuthorizationException;

Expand All @@ -83,6 +87,13 @@ public AuthState(@Nullable AuthorizationResponse authResponse,
update(authResponse, authError);
}

/**
* Creates an {@link AuthState} based on a dynamic registration client registration request.
*/
public AuthState(@NonNull RegistrationResponse regResponse) {
update(regResponse);
}

/**
* Creates an {@link AuthState} based on an authorization exchange and subsequent token
* exchange.
Expand Down Expand Up @@ -151,6 +162,20 @@ public TokenResponse getLastTokenResponse() {
return mLastTokenResponse;
}

/**
* The most recent client registration response used to update this authorization state.
*
* <p>
* It is rarely necessary to directly use the response; instead convenience methods are provided
* to retrieve the {@link #getClientSecret() client secret} and
* {@link #getClientSecretExpirationTime() client secret expiration}.
* </p>
*/
@Nullable
public RegistrationResponse getLastRegistrationResponse() {
return mLastRegistrationResponse;
}

/**
* The configuration of the authorization service associated with this authorization state.
*/
Expand Down Expand Up @@ -226,6 +251,31 @@ public String getIdToken() {
return null;
}

/**
* The current client secret, if available.
*/
public String getClientSecret() {
if (mLastRegistrationResponse != null) {
return mLastRegistrationResponse.clientSecret;
}

return null;
}

/**
* The expiration time of the current client credentials (if available), as milliseconds from
* the UNIX epoch (consistent with {@link System#currentTimeMillis()}). If the value is 0, the
* client credentials will not expire.
*/
@Nullable
public Long getClientSecretExpirationTime() {
if (mLastRegistrationResponse != null) {
return mLastRegistrationResponse.clientSecretExpiresAt;
}

return null;
}

/**
* Determines whether the current state represents a successful authorization,
* from which at least either an access token or an ID token have been retrieved.
Expand Down Expand Up @@ -282,6 +332,24 @@ public void setNeedsTokenRefresh(boolean needsTokenRefresh) {
mNeedsTokenRefreshOverride = needsTokenRefresh;
}

/**
* Determines whether the client credentials is considered to have expired. If no client
* credentials have been acquired, then this method will always return {@code false}
*/
public boolean hasClientSecretExpired() {
return hasClientSecretExpired(SystemClock.INSTANCE);
}

@VisibleForTesting
boolean hasClientSecretExpired(Clock clock) {
if (getClientSecretExpirationTime() == null || getClientSecretExpirationTime() == 0) {
// no explicit expiration time, and 0 means it will not expire
return false;
}

return getClientSecretExpirationTime() <= clock.getCurrentTimeMillis();
}

/**
* Updates the authorization state based on a new authorization response.
*/
Expand Down Expand Up @@ -346,6 +414,19 @@ public void update(
}
}

/**
* Updates the authorization state based on a new client registration response.
*/
public void update(@Nullable RegistrationResponse regResponse) {
mLastRegistrationResponse = regResponse;
/* a new client registration will have a new client id, so invalidate the current session */
mRefreshToken = null;
mScope = null;
mLastAuthorizationResponse = null;
mLastTokenResponse = null;
mAuthorizationException = null;
}

/**
* Ensures that a non-expired access token is available before invoking the provided action.
*/
Expand Down Expand Up @@ -469,6 +550,12 @@ public JSONObject jsonSerialize() {
KEY_LAST_TOKEN_RESPONSE,
mLastTokenResponse.jsonSerialize());
}
if (mLastRegistrationResponse != null) {
JsonUtil.put(
json,
KEY_LAST_REGISTRATION_RESPONSE,
mLastRegistrationResponse.jsonSerialize());
}
return json;
}

Expand Down Expand Up @@ -504,6 +591,10 @@ public static AuthState jsonDeserialize(@NonNull JSONObject json) throws JSONExc
state.mLastTokenResponse = TokenResponse.jsonDeserialize(
json.getJSONObject(KEY_LAST_TOKEN_RESPONSE));
}
if (json.has(KEY_LAST_REGISTRATION_RESPONSE)) {
state.mLastRegistrationResponse = RegistrationResponse.jsonDeserialize(
json.getJSONObject(KEY_LAST_REGISTRATION_RESPONSE));
}

return state;
}
Expand Down Expand Up @@ -535,4 +626,36 @@ void execute(
@Nullable String idToken,
@Nullable AuthorizationException ex);
}
/**
* Creates the required client authentication for the token endpoint based on information
* in the most recent registration response (if it is set).
*
* @throws ClientAuthentication.UnsupportedAuthenticationMethod if the expected client
* authentication method is unsupported by this client library.
*/
public ClientAuthentication getClientAuthentication() throws
ClientAuthentication.UnsupportedAuthenticationMethod {
if (getClientSecret() == null) {
/* Without client credentials, or unspecified 'token_endpoint_auth_method',
* we can never authenticate */
return NoClientAuthentication.INSTANCE;
} else if (mLastRegistrationResponse.tokenEndpointAuthMethod == null) {
/* 'token_endpoint_auth_method': "If omitted, the default is client_secret_basic",
* "OpenID Connect Dynamic Client Registration 1.0", Section 2 */
return new ClientSecretBasic(getClientSecret());
}

switch (mLastRegistrationResponse.tokenEndpointAuthMethod) {
case ClientSecretBasic.NAME:
return new ClientSecretBasic(getClientSecret());
case ClientSecretPost.NAME:
return new ClientSecretPost(getClientSecret());
case "none":
return NoClientAuthentication.INSTANCE;
default:
throw new ClientAuthentication.UnsupportedAuthenticationMethod(
mLastRegistrationResponse.tokenEndpointAuthMethod);

}
}
}