Skip to content

Commit

Permalink
Basic workflow to get an access token from OSM Oauth2 endpoints
Browse files Browse the repository at this point in the history
  • Loading branch information
mendhak committed Nov 1, 2023
1 parent d559b23 commit 621e7d8
Show file tree
Hide file tree
Showing 3 changed files with 150 additions and 106 deletions.
15 changes: 7 additions & 8 deletions gpslogger/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,12 @@
<data android:scheme="com.mendhak.gpslogger"
android:path="/oauth2googledrive"/>
</intent-filter>

<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:host="oauth2openstreetmap" android:scheme="gpslogger" />
</intent-filter>
</activity>

<activity
Expand All @@ -90,13 +95,7 @@
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="com.mendhak.gpslogger.GpsMainActivity" />
<intent-filter>
<action android:name="com.mendhak.gpslogger.MAIN_PREFS" />
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:host="authorize" android:scheme="gpslogger" />
</intent-filter>

</activity>

<activity
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@

package com.mendhak.gpslogger.senders.osm;

import android.content.Context;
import android.net.Uri;

import com.birbit.android.jobqueue.CancelResult;
import com.birbit.android.jobqueue.JobManager;
import com.birbit.android.jobqueue.TagConstraint;
Expand All @@ -27,6 +30,11 @@
import com.mendhak.gpslogger.common.Strings;
import com.mendhak.gpslogger.common.slf4j.Logs;
import com.mendhak.gpslogger.senders.FileSender;

import net.openid.appauth.AppAuthConfiguration;
import net.openid.appauth.AuthorizationService;
import net.openid.appauth.AuthorizationServiceConfiguration;

import oauth.signpost.OAuthConsumer;
import oauth.signpost.OAuthProvider;
import org.slf4j.Logger;
Expand All @@ -42,9 +50,9 @@ public class OpenStreetMapManager extends FileSender {


private static final Logger LOG = Logs.of(OpenStreetMapManager.class);
static final String OSM_REQUESTTOKEN_URL = "https://www.openstreetmap.org/oauth/request_token";
static final String OSM_ACCESSTOKEN_URL = "https://www.openstreetmap.org/oauth/access_token";
static final String OSM_AUTHORIZE_URL = "https://www.openstreetmap.org/oauth/authorize";
static final String OSM_REQUESTTOKEN_URL = "https://www.openstreetmap.org/oauth2/request_token";
static final String OSM_ACCESSTOKEN_URL = "https://www.openstreetmap.org/oauth2/access_token";
static final String OSM_AUTHORIZE_URL = "https://www.openstreetmap.org/oauth2/authorize";
static final String OSM_GPSTRACE_URL = "https://www.openstreetmap.org/api/0.6/gpx/create";
private PreferenceHelper preferenceHelper;

Expand All @@ -53,6 +61,35 @@ public OpenStreetMapManager(PreferenceHelper preferenceHelper) {

}

public static String getOpenStreetMapClientID() {
//OAuth Client for F-Droid release key
return "IPhwuq5DbvDXtP7VUKLU2x5TLEucQLHyKez8DdNNgVM";
// The Client ID doesn't matter too much, it needs to exist, but for verification what Android
// does is match by SHA1 signing key + package name.
}

public static String getOpenStreetMapRedirect() {
//Needs to match in androidmanifest.xml
return "gpslogger://oauth2openstreetmap";
}

public static String[] getOpenStreetMapClientScopes() {
return new String[]{"write_gpx"};
}

public static AuthorizationService getAuthorizationService(Context context) {
return new AuthorizationService(context, new AppAuthConfiguration.Builder().build());
}

public static AuthorizationServiceConfiguration getAuthorizationServiceConfiguration() {
return new AuthorizationServiceConfiguration(
Uri.parse("https://www.openstreetmap.org/oauth2/authorize"),
Uri.parse("https://www.openstreetmap.org/oauth2/token"),
null,
null
);
}

public static OAuthProvider getOSMAuthProvider() {
return new OkHttpOAuthProvider(OSM_REQUESTTOKEN_URL, OSM_ACCESSTOKEN_URL, OSM_AUTHORIZE_URL);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,21 @@
package com.mendhak.gpslogger.ui.fragments.settings;


import android.app.Activity;
import android.app.PendingIntent;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.util.Base64;

import androidx.activity.result.ActivityResult;
import androidx.activity.result.ActivityResultCallback;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.IntentSenderRequest;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.FragmentActivity;
import androidx.preference.Preference;
import androidx.preference.PreferenceFragmentCompat;
Expand All @@ -37,18 +46,30 @@
import com.mendhak.gpslogger.senders.osm.OpenStreetMapManager;
import com.mendhak.gpslogger.ui.Dialogs;

import net.openid.appauth.AuthState;
import net.openid.appauth.AuthorizationException;
import net.openid.appauth.AuthorizationRequest;
import net.openid.appauth.AuthorizationResponse;
import net.openid.appauth.AuthorizationService;
import net.openid.appauth.ClientAuthentication;
import net.openid.appauth.ClientSecretBasic;
import net.openid.appauth.ClientSecretPost;
import net.openid.appauth.ResponseTypeValues;
import net.openid.appauth.TokenRequest;
import net.openid.appauth.TokenResponse;

import org.slf4j.Logger;

import java.security.MessageDigest;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Arrays;

import eltos.simpledialogfragment.SimpleDialog;
import eltos.simpledialogfragment.form.Input;
import eltos.simpledialogfragment.form.SimpleFormDialog;
import eltos.simpledialogfragment.list.SimpleListDialog;
import oauth.signpost.OAuth;
import oauth.signpost.OAuthConsumer;
import oauth.signpost.OAuthProvider;


public class OSMAuthorizationFragment extends PreferenceFragmentCompat
implements Preference.OnPreferenceClickListener, SimpleDialog.OnDialogResultListener {
Expand All @@ -59,8 +80,10 @@ public class OSMAuthorizationFragment extends PreferenceFragmentCompat
private Handler osmHandler = new Handler();

//Must be static - when user returns from OSM, this needs to be set already
private static OAuthProvider provider;
private static OAuthConsumer consumer;
private AuthorizationService authorizationService;
private AuthState authState = new AuthState();


OpenStreetMapManager manager;

@Override
Expand All @@ -81,7 +104,6 @@ public void onCreate(Bundle savedInstanceState) {
LOG.debug("OAuth user has returned!");
String oAuthVerifier = myURI.getQueryParameter("oauth_verifier");

new Thread(new OsmAuthorizationEndWorkflow(oAuthVerifier)).start();

}

Expand Down Expand Up @@ -184,11 +206,38 @@ public boolean onPreferenceClick(Preference preference) {

} else {

authorizationService = OpenStreetMapManager.getAuthorizationService(getActivity());

SecureRandom sr = new SecureRandom();
byte[] ba = new byte[64];
sr.nextBytes(ba);
String codeVerifier = android.util.Base64.encodeToString(ba, Base64.URL_SAFE | Base64.NO_PADDING | Base64.NO_WRAP);

try {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] hash = digest.digest(codeVerifier.getBytes());
String codeChallenge = android.util.Base64.encodeToString(hash, Base64.URL_SAFE | Base64.NO_PADDING | Base64.NO_WRAP);

AuthorizationRequest.Builder requestBuilder = new AuthorizationRequest.Builder(
OpenStreetMapManager.getAuthorizationServiceConfiguration(),
OpenStreetMapManager.getOpenStreetMapClientID(),
ResponseTypeValues.CODE,
Uri.parse(OpenStreetMapManager.getOpenStreetMapRedirect())
).setCodeVerifier(codeVerifier, codeChallenge, "S256");

requestBuilder.setScopes(OpenStreetMapManager.getOpenStreetMapClientScopes());
AuthorizationRequest authRequest = requestBuilder.build();
Intent authIntent = authorizationService.getAuthorizationRequestIntent(authRequest);
openStreetMapAuthenticationWorkflow.launch(new IntentSenderRequest.Builder(
PendingIntent.getActivity(getActivity(), 0, authIntent, 0))
.setFillInIntent(authIntent)
.build());

} catch (Exception e) {
LOG.error(e.getMessage(), e);
}


//User clicks. Set the consumer and provider up.
consumer = manager.getOSMAuthConsumer();
provider = manager.getOSMAuthProvider();
new Thread(new OsmAuthorizationBeginWorkflow()).start();
}
return true;
}
Expand All @@ -197,6 +246,50 @@ public boolean onPreferenceClick(Preference preference) {
return true;
}

ActivityResultLauncher<IntentSenderRequest> openStreetMapAuthenticationWorkflow = registerForActivityResult(
new ActivityResultContracts.StartIntentSenderForResult(),
new ActivityResultCallback<ActivityResult>() {
@Override
public void onActivityResult(ActivityResult result) {
if (result.getResultCode() == Activity.RESULT_OK) {
LOG.debug(String.valueOf(result.getData()));
AuthorizationResponse authResponse = AuthorizationResponse.fromIntent(result.getData());
AuthorizationException authException = AuthorizationException.fromIntent(result.getData());
authState = new AuthState(authResponse, authException);
if (authException != null) {
LOG.error(authException.toJsonString(), authException);
}
if (authResponse != null) {
// ClientAuthentication clientAuth = new ClientSecretPost(OpenStreetMapManager.getOpenStreetMapClientSecret());
TokenRequest tokenRequest = authResponse.createTokenExchangeRequest();

authorizationService.performTokenRequest(tokenRequest, new AuthorizationService.TokenResponseCallback() {
@Override
public void onTokenRequestCompleted(@Nullable TokenResponse response, @Nullable AuthorizationException ex) {
if (ex != null) {
authState = new AuthState();
LOG.error(ex.toJsonString(), ex);
} else {
if (response != null) {
authState.update(response, ex);

}
}
// saveOSMAuthState();
// save the auth state to preferences now
LOG.info(authState.jsonSerializeString());
setPreferencesState();


}
});
}

}

}
});

@Override
public boolean onResult(@NonNull String dialogTag, int which, @NonNull Bundle extras) {
if(which != BUTTON_POSITIVE){
Expand Down Expand Up @@ -227,91 +320,6 @@ public boolean onResult(@NonNull String dialogTag, int which, @NonNull Bundle ex
return false;
}

private class OsmAuthorizationEndWorkflow implements Runnable {

String oAuthVerifier;

OsmAuthorizationEndWorkflow(String oAuthVerifier) {
this.oAuthVerifier = oAuthVerifier;
}

@Override
public void run() {
try {
if (provider == null) {
provider = OpenStreetMapManager.getOSMAuthProvider();
}

if (consumer == null) {
//In case consumer is null, re-initialize from stored values.
consumer = OpenStreetMapManager.getOSMAuthConsumer();
}


//Ask OpenStreetMap for the access token. This is the main event.
provider.retrieveAccessToken(consumer, oAuthVerifier);

String osmAccessToken = consumer.getToken();
String osmAccessTokenSecret = consumer.getTokenSecret();

//Save for use later.
preferenceHelper.setOSMAccessToken(osmAccessToken);
preferenceHelper.setOSMAccessTokenSecret(osmAccessTokenSecret);

osmHandler.post(new Runnable() {
@Override
public void run() {
Dialogs.hideProgress();
setPreferencesState();
}
});


} catch (final Exception e) {
LOG.error("OSM authorization error", e);
osmHandler.post(new Runnable() {
@Override
public void run() {
Dialogs.hideProgress();
if(getActivity()!=null && isAdded()) {
Dialogs.showError(getString(R.string.sorry), getString(R.string.osm_auth_error), e.getMessage(), e, (FragmentActivity) getActivity());
}
}
});
}
}
}

private class OsmAuthorizationBeginWorkflow implements Runnable {

@Override
public void run() {
try {
String authUrl;
//Get the request token and request token secret
authUrl = provider.retrieveRequestToken(consumer, OAuth.OUT_OF_BAND);

//Save for later
preferenceHelper.setOSMRequestToken(consumer.getToken());
preferenceHelper.setOSMRequestTokenSecret(consumer.getTokenSecret());


//Open browser, send user to OpenStreetMap.org
Uri uri = Uri.parse(authUrl);
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
startActivity(intent);
} catch (final Exception e) {
LOG.error("onClick", e);
osmHandler.post(new Runnable() {
@Override
public void run() {
if(getActivity()!=null && isAdded()){
Dialogs.showError(getString(R.string.sorry), getString(R.string.osm_auth_error), e.getMessage(), e,
(FragmentActivity) getActivity());
}
}
});
}
}
}
}

0 comments on commit 621e7d8

Please sign in to comment.