Skip to content

Commit

Permalink
Support OAuth 2
Browse files Browse the repository at this point in the history
This adds a minimal implementation of OAuth 2 as that works (likely
only) with the current OpenStreetMap API.

Fixes #2401
  • Loading branch information
simonpoole committed Jan 6, 2024
1 parent e39b41e commit 1f6e56b
Show file tree
Hide file tree
Showing 10 changed files with 661 additions and 354 deletions.
36 changes: 21 additions & 15 deletions src/main/java/de/blau/android/Authorize.java
Expand Up @@ -18,8 +18,10 @@
import de.blau.android.contract.Schemes;
import de.blau.android.dialogs.Progress;
import de.blau.android.exception.OsmException;
import de.blau.android.net.OAuthHelper;
import de.blau.android.net.OAuth2Helper;
import de.blau.android.net.OAuth1aHelper;
import de.blau.android.osm.Server;
import de.blau.android.prefs.API.Auth;
import de.blau.android.prefs.Preferences;
import de.blau.android.util.ActivityResultHandler;
import de.blau.android.util.ScreenMessage;
Expand All @@ -28,7 +30,7 @@
import oauth.signpost.exception.OAuthException;

/**
* Perform OAuth authorisation of this app
* Perform OAuth 1/2 authorisation of this app
*
* @author simon
*
Expand Down Expand Up @@ -131,31 +133,35 @@ protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

Server server = prefs.getServer();

String apiName = server.getApiName();
OAuthHelper oa;
try {
oa = new OAuthHelper(this, apiName);
} catch (OsmException oe) {
server.setOAuth(false); // ups something went wrong turn oauth off
ScreenMessage.barError(this, getString(R.string.toast_no_oauth, apiName));
return;
}
Log.d(DEBUG_TAG, "oauth auth for " + apiName);
Auth auth = server.getAuthentication();
Log.d(DEBUG_TAG, "oauth auth for " + apiName + " " + auth);

String authUrl = null;
String errorMessage = null;
try {
authUrl = oa.getRequestToken();
if (auth == Auth.OAUTH1A) {
OAuth1aHelper oa = new OAuth1aHelper(this, apiName);
authUrl = oa.getRequestToken();
} else if (auth == Auth.OAUTH2) {
OAuth2Helper oa = new OAuth2Helper(this, apiName);
authUrl = oa.getAuthorisationUrl(this);
}
} catch (OsmException oe) {
server.setOAuth(false); // ups something went wrong turn oauth off
errorMessage = getString(R.string.toast_no_oauth, apiName);
} catch (OAuthException e) {
errorMessage = OAuthHelper.getErrorMessage(this, e);
errorMessage = OAuth1aHelper.getErrorMessage(this, e);
} catch (ExecutionException e) {
errorMessage = getString(R.string.toast_oauth_communication);
} catch (TimeoutException e) {
errorMessage = getString(R.string.toast_oauth_timeout);
}
if (authUrl == null) {
ScreenMessage.barError(this, errorMessage);
Log.e(DEBUG_TAG, "onCreate error " + errorMessage);
if (errorMessage != null) {
ScreenMessage.barError(this, errorMessage);
}
return;
}
Log.d(DEBUG_TAG, "authURl " + authUrl);
Expand Down
207 changes: 207 additions & 0 deletions src/main/java/de/blau/android/net/OAuth1aHelper.java
@@ -0,0 +1,207 @@
package de.blau.android.net;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import android.content.Context;
import android.net.Uri;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import de.blau.android.App;
import de.blau.android.PostAsyncActionHandler;
import de.blau.android.exception.OsmException;
import de.blau.android.prefs.API.Auth;
import de.blau.android.resources.KeyDatabaseHelper;
import de.blau.android.util.ExecutorTask;
import oauth.signpost.OAuthConsumer;
import oauth.signpost.OAuthProvider;
import oauth.signpost.exception.OAuthCommunicationException;
import oauth.signpost.exception.OAuthException;
import oauth.signpost.exception.OAuthExpectationFailedException;
import oauth.signpost.exception.OAuthMessageSignerException;
import oauth.signpost.exception.OAuthNotAuthorizedException;
import se.akerfeldt.okhttp.signpost.OkHttpOAuthConsumer;
import se.akerfeldt.okhttp.signpost.OkHttpOAuthProvider;

/**
* Helper class for signpost oAuth more or less based on text below
*
*/
public class OAuth1aHelper extends OAuthHelper {
private static final String DEBUG_TAG = OAuth1aHelper.class.getSimpleName();

private static final String CALLBACK_URL = "vespucci:/oauth/";
private static final String AUTHORIZE_PATH = "oauth/authorize";
private static final String ACCESS_TOKEN_PATH = "oauth/access_token";
private static final String REQUEST_TOKEN_PATH = "oauth/request_token";

private static final String OAUTH_VERIFIER_PARAMTER = "oauth_verifier";
private static final String OAUTH_TOKEN_PARAMETER = "oauth_token";

private static OAuthConsumer mConsumer;
private static OAuthProvider mProvider;
private static String mCallbackUrl;

/**
* Construct a new helper instance
*
* @param context an Android Context
* @param apiName the base URL for the API instance
*
* @throws OsmException if no configuration could be found for the API instance
*/
public OAuth1aHelper(@NonNull Context context, @NonNull String apiName) throws OsmException {
try (KeyDatabaseHelper keyDatabase = new KeyDatabaseHelper(context)) {
OAuthConfiguration configuration = KeyDatabaseHelper.getOAuthConfiguration(keyDatabase.getReadableDatabase(), apiName, Auth.OAUTH1A);
if (configuration != null) {
init(configuration.getKey(), configuration.getSecret(), configuration.getOauthUrl());
return;
}
logMissingApi(apiName);
throw new OsmException("No matching OAuth configuration found for API " + apiName);
}
}

/**
* Initialize the fields
*
* @param key OAuth 1a key
* @param secret OAuth 1a secret
* @param oauthUrl URL to use for authorization
*/
private static void init(String key, String secret, String oauthUrl) {
mConsumer = new OkHttpOAuthConsumer(key, secret);
mProvider = new OkHttpOAuthProvider(oauthUrl + REQUEST_TOKEN_PATH, oauthUrl + ACCESS_TOKEN_PATH, oauthUrl + AUTHORIZE_PATH, App.getHttpClient());
mProvider.setOAuth10a(true);
mCallbackUrl = CALLBACK_URL;
}

/**
* this constructor is for access to the singletons
*/
public OAuth1aHelper() {
}

/**
* Returns an OAuthConsumer initialized with the consumer keys for the API in question
*
* @param context an Android Context
* @param apiName the name of the API configuration
*
* @return an initialized OAuthConsumer or null if something blows up
*/
@Nullable
public OkHttpOAuthConsumer getOkHttpConsumer(Context context, @NonNull String apiName) {
try (KeyDatabaseHelper keyDatabase = new KeyDatabaseHelper(context)) {
OAuthConfiguration configuration = KeyDatabaseHelper.getOAuthConfiguration(keyDatabase.getReadableDatabase(), apiName, Auth.OAUTH1A);
if (configuration != null) {
return new OkHttpOAuthConsumer(configuration.getKey(), configuration.getSecret());
}
logMissingApi(apiName);
return null;
}
}

/**
* Get the request token
*
* @return the token or null
* @throws OAuthException if an error happened during the OAuth handshake
* @throws TimeoutException if we waited too long for a response
* @throws ExecutionException
*/
public String getRequestToken() throws OAuthException, TimeoutException, ExecutionException {
Log.d(DEBUG_TAG, "getRequestToken");
class RequestTokenTask extends ExecutorTask<Void, Void, String> {
private OAuthException ex = null;

/**
* Create a new RequestTokenTask
*
* @param executorService ExecutorService to run this on
* @param handler an Handler
*/
RequestTokenTask() {
super();
}

@Override
protected String doInBackground(Void param) {
try {
return mProvider.retrieveRequestToken(mConsumer, mCallbackUrl);
} catch (OAuthException e) {
Log.d(DEBUG_TAG, "getRequestToken " + e);
ex = e;
}
return null;
}

/**
* Get the any OAuthException that was thrown
*
* @return the exception
*/
OAuthException getException() {
return ex;
}
}

RequestTokenTask requester = new RequestTokenTask();
requester.execute();
String result = null;
try {
result = requester.get(10, TimeUnit.SECONDS);
} catch (InterruptedException e) { // NOSONAR cancel does interrupt the thread in question
requester.cancel();
throw new TimeoutException(e.getMessage());
}
if (result == null) {
OAuthException ex = requester.getException();
if (ex != null) {
throw ex;
}
}
return result;
}

@Override
protected ExecutorTask<?, ?, ?> getAccessTokenTask(Context context, Uri data, PostAsyncActionHandler handler) {
String oauthToken = data.getQueryParameter(OAUTH_TOKEN_PARAMETER);
final String oauthVerifier = data.getQueryParameter(OAUTH_VERIFIER_PARAMTER);

if ((oauthToken == null) && (oauthVerifier == null)) {
Log.i(DEBUG_TAG, "got oauth verifier " + oauthToken + " " + oauthVerifier);
throw new IllegalArgumentException("No token or verifier");
}
return new ExecutorTask<Void, Void, Boolean>() {

@Override
protected Boolean doInBackground(Void arg)
throws OAuthMessageSignerException, OAuthNotAuthorizedException, OAuthExpectationFailedException, OAuthCommunicationException {
if (mProvider == null || mConsumer == null) {
throw new OAuthExpectationFailedException("OAuthHelper not initialized!");
}
mProvider.retrieveAccessToken(mConsumer, oauthVerifier);
setAccessToken(context, mConsumer.getToken(), mConsumer.getTokenSecret());
return true;
}

@Override
protected void onBackgroundError(Exception e) {
handler.onError(null);
}

@Override
protected void onPostExecute(Boolean success) {
Log.d(DEBUG_TAG, "oAuthHandshake onPostExecute");
if (success != null && success) {
handler.onSuccess();
} else {
handler.onError(null);
}
}
};
}
}

0 comments on commit 1f6e56b

Please sign in to comment.