mirror of https://github.com/M66B/FairEmail.git
Gmail OAuth - proof of concept
This commit is contained in:
parent
916a966ce4
commit
f496a0fa6c
|
@ -19,3 +19,4 @@ FairEmail uses:
|
|||
* [Markwon](https://github.com/noties/Markwon). Copyright 2019 Dimitry Ivanov. [Apache License 2.0](https://github.com/noties/Markwon/blob/master/LICENSE).
|
||||
* [Color Picker](https://github.com/QuadFlask/colorpicker). Copyright 2014-2017 QuadFlask. [Apache License 2.0](https://github.com/QuadFlask/colorpicker#user-content-license).
|
||||
* [Bouncy Castle](https://www.bouncycastle.org/). Copyright (c) 2000 - 2019 The Legion of the Bouncy Castle Inc. [MIT License](https://www.bouncycastle.org/licence.html).
|
||||
* [AppAuth for Android](https://github.com/openid/AppAuth-Android). Copyright 2015 The AppAuth for Android Authors. All Rights Reserved. [Apache License 2.0](https://github.com/openid/AppAuth-Android/blob/master/LICENSE).
|
||||
|
|
|
@ -214,6 +214,7 @@ dependencies {
|
|||
def msal_version = "1.0.0"
|
||||
def bouncycastle_version = "1.64"
|
||||
def colorpicker_version = "0.0.15"
|
||||
def appauth_version = "0.7.1"
|
||||
|
||||
// https://developer.android.com/jetpack/androidx/releases/
|
||||
|
||||
|
@ -339,4 +340,8 @@ dependencies {
|
|||
// https://mvnrepository.com/artifact/org.bouncycastle/bcpkix-jdk15on
|
||||
implementation "org.bouncycastle:bcpkix-jdk15to18:$bouncycastle_version"
|
||||
//implementation "org.bouncycastle:bcmail-jdk15to18:$bouncycastle_version"
|
||||
|
||||
// https://github.com/openid/AppAuth-Android
|
||||
// https://mvnrepository.com/artifact/net.openid/appauth?repo=bt-openid
|
||||
implementation "net.openid:appauth:$appauth_version"
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="eu.faircode.email">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
|
@ -227,6 +228,22 @@
|
|||
android:icon="@mipmap/ic_launcher"
|
||||
android:launchMode="singleTask" />
|
||||
|
||||
<activity
|
||||
android:name="net.openid.appauth.RedirectUriReceiverActivity"
|
||||
tools:node="replace">
|
||||
<intent-filter android:autoVerify="true">
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data
|
||||
android:host="email.faircode.eu"
|
||||
android:path="/oauth/"
|
||||
android:scheme="https" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<service
|
||||
android:name=".ServiceSynchronize"
|
||||
android:foregroundServiceType="dataSync" />
|
||||
|
|
|
@ -19,3 +19,4 @@ FairEmail uses:
|
|||
* [Markwon](https://github.com/noties/Markwon). Copyright 2019 Dimitry Ivanov. [Apache License 2.0](https://github.com/noties/Markwon/blob/master/LICENSE).
|
||||
* [Color Picker](https://github.com/QuadFlask/colorpicker). Copyright 2014-2017 QuadFlask. [Apache License 2.0](https://github.com/QuadFlask/colorpicker#user-content-license).
|
||||
* [Bouncy Castle](https://www.bouncycastle.org/). Copyright (c) 2000 - 2019 The Legion of the Bouncy Castle Inc. [MIT License](https://www.bouncycastle.org/licence.html).
|
||||
* [AppAuth for Android](https://github.com/openid/AppAuth-Android). Copyright 2015 The AppAuth for Android Authors. All Rights Reserved. [Apache License 2.0](https://github.com/openid/AppAuth-Android/blob/master/LICENSE).
|
||||
|
|
|
@ -73,6 +73,21 @@ import com.microsoft.identity.client.IPublicClientApplication;
|
|||
import com.microsoft.identity.client.PublicClientApplication;
|
||||
import com.microsoft.identity.client.exception.MsalException;
|
||||
|
||||
import net.openid.appauth.AppAuthConfiguration;
|
||||
import net.openid.appauth.AuthorizationException;
|
||||
import net.openid.appauth.AuthorizationRequest;
|
||||
import net.openid.appauth.AuthorizationResponse;
|
||||
import net.openid.appauth.AuthorizationService;
|
||||
import net.openid.appauth.AuthorizationServiceConfiguration;
|
||||
import net.openid.appauth.ClientAuthentication;
|
||||
import net.openid.appauth.ClientSecretPost;
|
||||
import net.openid.appauth.ResponseTypeValues;
|
||||
import net.openid.appauth.TokenResponse;
|
||||
import net.openid.appauth.browser.BrowserBlacklist;
|
||||
import net.openid.appauth.browser.Browsers;
|
||||
import net.openid.appauth.browser.VersionRange;
|
||||
import net.openid.appauth.browser.VersionedBrowserMatcher;
|
||||
|
||||
import org.bouncycastle.util.io.pem.PemObject;
|
||||
import org.bouncycastle.util.io.pem.PemReader;
|
||||
import org.json.JSONArray;
|
||||
|
@ -131,8 +146,10 @@ public class ActivitySetup extends ActivityBase implements FragmentManager.OnBac
|
|||
static final int REQUEST_CHOOSE_ACCOUNT = 5;
|
||||
static final int REQUEST_DONE = 6;
|
||||
static final int REQUEST_IMPORT_CERTIFICATE = 7;
|
||||
static final int REQUEST_OAUTH = 8;
|
||||
|
||||
static final String ACTION_QUICK_GMAIL = BuildConfig.APPLICATION_ID + ".ACTION_QUICK_GMAIL";
|
||||
static final String ACTION_QUICK_OAUTH = BuildConfig.APPLICATION_ID + ".ACTION_QUICK_OAUTH";
|
||||
static final String ACTION_QUICK_OUTLOOK = BuildConfig.APPLICATION_ID + ".ACTION_QUICK_OUTLOOK";
|
||||
static final String ACTION_QUICK_SETUP = BuildConfig.APPLICATION_ID + ".ACTION_QUICK_SETUP";
|
||||
static final String ACTION_VIEW_ACCOUNTS = BuildConfig.APPLICATION_ID + ".ACTION_VIEW_ACCOUNTS";
|
||||
|
@ -310,6 +327,7 @@ public class ActivitySetup extends ActivityBase implements FragmentManager.OnBac
|
|||
LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(this);
|
||||
IntentFilter iff = new IntentFilter();
|
||||
iff.addAction(ACTION_QUICK_GMAIL);
|
||||
iff.addAction(ACTION_QUICK_OAUTH);
|
||||
iff.addAction(ACTION_QUICK_OUTLOOK);
|
||||
iff.addAction(ACTION_QUICK_SETUP);
|
||||
iff.addAction(ACTION_VIEW_ACCOUNTS);
|
||||
|
@ -383,6 +401,10 @@ public class ActivitySetup extends ActivityBase implements FragmentManager.OnBac
|
|||
if (resultCode == RESULT_OK && data != null)
|
||||
handleImportCertificate(data);
|
||||
break;
|
||||
case REQUEST_OAUTH:
|
||||
if (resultCode == RESULT_OK && data != null)
|
||||
onHandleOAuth(data);
|
||||
break;
|
||||
}
|
||||
} catch (Throwable ex) {
|
||||
Log.e(ex);
|
||||
|
@ -1138,6 +1160,78 @@ public class ActivitySetup extends ActivityBase implements FragmentManager.OnBac
|
|||
fragmentTransaction.commit();
|
||||
}
|
||||
|
||||
private void onOAuth(Intent intent) {
|
||||
String name = intent.getStringExtra("name");
|
||||
for (EmailProvider provider : EmailProvider.loadProfiles(this))
|
||||
if (provider.name.equals(name) && provider.oauth != null) {
|
||||
AppAuthConfiguration appAuthConfig = new AppAuthConfiguration.Builder()
|
||||
.setBrowserMatcher(new BrowserBlacklist(
|
||||
new VersionedBrowserMatcher(
|
||||
Browsers.SBrowser.PACKAGE_NAME,
|
||||
Browsers.SBrowser.SIGNATURE_SET,
|
||||
true,
|
||||
VersionRange.atMost("5.3")
|
||||
)))
|
||||
.build();
|
||||
|
||||
AuthorizationService authService = new AuthorizationService(this, appAuthConfig);
|
||||
|
||||
AuthorizationRequest authRequest =
|
||||
new AuthorizationRequest.Builder(
|
||||
new AuthorizationServiceConfiguration(
|
||||
Uri.parse(provider.oauth.authorizationEndpoint),
|
||||
Uri.parse(provider.oauth.tokenEndpoint)),
|
||||
provider.oauth.clientId,
|
||||
ResponseTypeValues.CODE,
|
||||
Uri.parse(provider.oauth.redirectUri))
|
||||
.setScopes(provider.oauth.scopes)
|
||||
.setState(name)
|
||||
.build();
|
||||
|
||||
Intent authIntent = authService.getAuthorizationRequestIntent(authRequest);
|
||||
startActivityForResult(authIntent, REQUEST_OAUTH);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Log.unexpectedError(getSupportFragmentManager(),
|
||||
new IllegalArgumentException("Unknown provider=" + name));
|
||||
}
|
||||
|
||||
private void onHandleOAuth(Intent data) {
|
||||
AuthorizationResponse auth = AuthorizationResponse.fromIntent(data);
|
||||
if (auth == null) {
|
||||
AuthorizationException ex = AuthorizationException.fromIntent(data);
|
||||
Log.unexpectedError(getSupportFragmentManager(), ex);
|
||||
return;
|
||||
}
|
||||
|
||||
for (EmailProvider provider : EmailProvider.loadProfiles(this))
|
||||
if (provider.name.equals(auth.state)) {
|
||||
AuthorizationService authService = new AuthorizationService(this);
|
||||
ClientAuthentication clientAuth = new ClientSecretPost(provider.oauth.clientSecret);
|
||||
authService.performTokenRequest(
|
||||
auth.createTokenExchangeRequest(),
|
||||
clientAuth,
|
||||
new AuthorizationService.TokenResponseCallback() {
|
||||
@Override
|
||||
public void onTokenRequestCompleted(TokenResponse access, AuthorizationException ex) {
|
||||
if (access == null) {
|
||||
Log.unexpectedError(getSupportFragmentManager(), ex);
|
||||
return;
|
||||
}
|
||||
|
||||
// access.accessToken
|
||||
}
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Log.unexpectedError(getSupportFragmentManager(),
|
||||
new IllegalArgumentException("Unknown state=" + auth.state));
|
||||
}
|
||||
|
||||
private void onOutlook(Intent intent) {
|
||||
PublicClientApplication.createMultipleAccountPublicClientApplication(
|
||||
this,
|
||||
|
@ -1484,6 +1578,8 @@ public class ActivitySetup extends ActivityBase implements FragmentManager.OnBac
|
|||
String action = intent.getAction();
|
||||
if (ACTION_QUICK_GMAIL.equals(action))
|
||||
onGmail(intent);
|
||||
else if (ACTION_QUICK_OAUTH.equals(action))
|
||||
onOAuth(intent);
|
||||
else if (ACTION_QUICK_OUTLOOK.equals(action))
|
||||
onOutlook(intent);
|
||||
else if (ACTION_QUICK_SETUP.equals(action))
|
||||
|
|
|
@ -67,6 +67,7 @@ public class EmailProvider {
|
|||
public String link;
|
||||
public Server imap = new Server();
|
||||
public Server smtp = new Server();
|
||||
public OAuth oauth;
|
||||
public UserType user = UserType.EMAIL;
|
||||
public StringBuilder documentation; // html
|
||||
|
||||
|
@ -129,6 +130,14 @@ public class EmailProvider {
|
|||
provider.smtp.host = xml.getAttributeValue(null, "host");
|
||||
provider.smtp.port = xml.getAttributeIntValue(null, "port", 0);
|
||||
provider.smtp.starttls = xml.getAttributeBooleanValue(null, "starttls", false);
|
||||
} else if ("oauth".equals(name)) {
|
||||
provider.oauth = new OAuth();
|
||||
provider.oauth.clientId = xml.getAttributeValue(null, "clientId");
|
||||
provider.oauth.clientSecret = xml.getAttributeValue(null, "clientSecret");
|
||||
provider.oauth.scopes = xml.getAttributeValue(null, "scopes").split(",");
|
||||
provider.oauth.authorizationEndpoint = xml.getAttributeValue(null, "authorizationEndpoint");
|
||||
provider.oauth.tokenEndpoint = xml.getAttributeValue(null, "tokenEndpoint");
|
||||
provider.oauth.redirectUri = xml.getAttributeValue(null, "redirectUri");
|
||||
} else
|
||||
throw new IllegalAccessException(name);
|
||||
} else if (eventType == XmlPullParser.END_TAG) {
|
||||
|
@ -650,4 +659,13 @@ public class EmailProvider {
|
|||
return host + ":" + port;
|
||||
}
|
||||
}
|
||||
|
||||
public static class OAuth {
|
||||
String clientId;
|
||||
String clientSecret;
|
||||
String[] scopes;
|
||||
String authorizationEndpoint;
|
||||
String tokenEndpoint;
|
||||
String redirectUri;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -167,9 +167,16 @@ public class FragmentSetup extends FragmentBase {
|
|||
PopupMenuLifecycle popupMenu = new PopupMenuLifecycle(getContext(), getViewLifecycleOwner(), btnQuick);
|
||||
|
||||
popupMenu.getMenu().add(Menu.NONE, R.string.title_setup_gmail, 1, R.string.title_setup_gmail);
|
||||
//popupMenu.getMenu().add(Menu.NONE, R.string.title_setup_outlook, 2, R.string.title_setup_outlook);
|
||||
popupMenu.getMenu().add(Menu.NONE, R.string.title_setup_activesync, 3, R.string.title_setup_activesync);
|
||||
popupMenu.getMenu().add(Menu.NONE, R.string.title_setup_other, 4, R.string.title_setup_other);
|
||||
|
||||
// Android 5 Lollipop does not support app links
|
||||
if (BuildConfig.DEBUG && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
|
||||
popupMenu.getMenu().add(Menu.NONE, R.string.title_setup_gmail_oauth, 2, R.string.title_setup_gmail_oauth);
|
||||
|
||||
if (BuildConfig.DEBUG)
|
||||
popupMenu.getMenu().add(Menu.NONE, R.string.title_setup_outlook, 3, R.string.title_setup_outlook);
|
||||
|
||||
popupMenu.getMenu().add(Menu.NONE, R.string.title_setup_activesync, 4, R.string.title_setup_activesync);
|
||||
popupMenu.getMenu().add(Menu.NONE, R.string.title_setup_other, 5, R.string.title_setup_other);
|
||||
|
||||
popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
|
||||
@Override
|
||||
|
@ -182,6 +189,9 @@ public class FragmentSetup extends FragmentBase {
|
|||
else
|
||||
ToastEx.makeText(getContext(), R.string.title_setup_gmail_support, Toast.LENGTH_LONG).show();
|
||||
return true;
|
||||
case R.string.title_setup_gmail_oauth:
|
||||
lbm.sendBroadcast(new Intent(ActivitySetup.ACTION_QUICK_OAUTH).putExtra("name", "Gmail"));
|
||||
return true;
|
||||
case R.string.title_setup_outlook:
|
||||
lbm.sendBroadcast(new Intent(ActivitySetup.ACTION_QUICK_OUTLOOK));
|
||||
return true;
|
||||
|
|
|
@ -140,6 +140,7 @@
|
|||
<string name="title_setup_wizard">Wizard</string>
|
||||
<string name="title_setup_wizard_remark">Go \'back\' to go to the inbox</string>
|
||||
<string name="title_setup_gmail" translatable="false">Gmail</string>
|
||||
<string name="title_setup_gmail_oauth" translatable="false">Gmail OAuth</string>
|
||||
<string name="title_setup_outlook" translatable="false">Outlook</string>
|
||||
<string name="title_setup_activesync" translatable="false">Exchange ActiveSync</string>
|
||||
<string name="title_setup_other">Other provider</string>
|
||||
|
|
|
@ -14,6 +14,15 @@
|
|||
host="smtp.gmail.com"
|
||||
port="465"
|
||||
starttls="false" />
|
||||
<oauth
|
||||
authorizationEndpoint="https://accounts.google.com/o/oauth2/v2/auth"
|
||||
clientId="803253368361-574lor1js3csqif9nogkhk5m7688af3c.apps.googleusercontent.com"
|
||||
clientSecret="9iyiDx1LEfpg3fpH6DqzoIcG"
|
||||
redirectUri="https://email.faircode.eu/oauth/"
|
||||
scopes="https://mail.google.com/"
|
||||
tokenEndpoint="https://oauth2.googleapis.com/token" />
|
||||
<!-- https://email.faircode.eu/.well-known/assetlinks.json -->
|
||||
<!-- /opt/android-studio/jre/bin/keytool -keystore ~/.android/debug.keystore -list -v -->
|
||||
</provider>
|
||||
<provider
|
||||
name="Outlook/Office365"
|
||||
|
|
Loading…
Reference in New Issue