diff --git a/app/build.gradle b/app/build.gradle
index 196b58df7f..de75d591be 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -49,6 +49,7 @@ android {
exclude 'META-INF/LICENSE.txt'
exclude 'META-INF/README.md'
exclude 'META-INF/CHANGES'
+ exclude 'META-INF/jersey-module-version'
}
signingConfigs {
@@ -202,6 +203,7 @@ dependencies {
def photoview_version = "2.3.0"
def relinker_version = "1.3.1"
def markwon_version = "4.1.1"
+ def msal_version = "1.0.0"
// https://developer.android.com/jetpack/androidx/releases/
@@ -319,4 +321,7 @@ dependencies {
// // https://github.com/QuadFlask/colorpicker
implementation project(':qcolorpicker')
+
+ // https://github.com/AzureAD/microsoft-authentication-library-for-android
+ implementation "com.microsoft.identity.client:msal:$msal_version"
}
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
index 6a37aca655..7714e6ac4b 100644
--- a/app/proguard-rules.pro
+++ b/app/proguard-rules.pro
@@ -73,6 +73,12 @@
-keepnames class biweekly.** {*;}
-dontwarn biweekly.io.json.**
+#MSAL
+-keep class com.microsoft.aad.adal.** {*;}
+-keep class com.microsoft.identity.common.** {*;}
+-dontwarn com.nimbusds.jose.**
+-keepclassmembers enum * {*;} #GSON
+
#Notes
-dontnote com.google.android.material.**
-dontnote com.sun.mail.**
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 79e95cc63e..adeb36e621 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -205,6 +205,20 @@
+
+
+
+
+
+
+
+
+
+
+
claims = result.getAccount().getClaims();
+ if (claims != null)
+ for (String key : claims.keySet())
+ Log.i(key + "=" + claims.get(key));
+
+ new SimpleTask() {
+ @Override
+ protected JSONObject onExecute(Context context, Bundle args) throws Throwable {
+ String token = args.getString("token");
+
+ // https://docs.microsoft.com/en-us/graph/api/user-get?view=graph-rest-1.0&tabs=http#http-request
+ URL url = new URL("https://graph.microsoft.com/v1.0/me" +
+ "?$select=displayName,otherMails");
+ Log.i("MSAL fetching " + url);
+
+ HttpURLConnection request = (HttpURLConnection) url.openConnection();
+ request.setReadTimeout(15 * 1000);
+ request.setConnectTimeout(15 * 1000);
+ request.setRequestMethod("GET");
+ request.setDoInput(true);
+ request.setRequestProperty("Authorization", "Bearer " + token);
+ request.setRequestProperty("Content-Type", "application/json");
+ request.connect();
+
+ try {
+ Log.i("MSAL getting response");
+ String json = Helper.readStream(request.getInputStream(), StandardCharsets.UTF_8.name());
+ return new JSONObject(json);
+ } finally {
+ request.disconnect();
+ }
+ }
+
+ @Override
+ protected void onExecuted(Bundle args, JSONObject data) {
+ Log.i("MSAL " + data);
+
+ try {
+ JSONArray otherMails = data.getJSONArray("otherMails");
+
+ args.putString("displayName", data.getString("displayName"));
+ args.putString("email", (String) otherMails.get(0));
+
+ new SimpleTask() {
+ @Override
+ protected Void onExecute(Context context, Bundle args) throws Throwable {
+ String token = args.getString("token");
+ String email = args.getString("id");
+ String displayName = args.getString("displayName");
+
+ List folders;
+
+ // https://msdn.microsoft.com/en-us/windows/desktop/dn440163
+ String host = "imap-mail.outlook.com";
+ int port = 993;
+ boolean starttls = false;
+ String user = email;
+ String password = token;
+ try (MailService iservice = new MailService(context, "imaps", null, false, true, true)) {
+ iservice.connect(host, port, MailService.AUTH_TYPE_OUTLOOK, user, password);
+
+ folders = iservice.getFolders();
+
+ DB db = DB.getInstance(context);
+ try {
+ db.beginTransaction();
+
+ EntityAccount primary = db.account().getPrimaryAccount();
+
+ // Create account
+ EntityAccount account = new EntityAccount();
+
+ account.host = host;
+ account.starttls = starttls;
+ account.port = port;
+ account.auth_type = MailService.AUTH_TYPE_OUTLOOK;
+ account.user = user;
+ account.password = password;
+
+ account.name = "OutLook";
+
+ account.synchronize = true;
+ account.primary = (primary == null);
+
+ account.created = new Date().getTime();
+ account.last_connected = account.created;
+
+ account.id = db.account().insertAccount(account);
+ args.putLong("account", account.id);
+ EntityLog.log(context, "OutLook account=" + account.name);
+
+ // Create folders
+ for (EntityFolder folder : folders) {
+ folder.account = account.id;
+ folder.id = db.folder().insertFolder(folder);
+ EntityLog.log(context, "OutLook folder=" + folder.name + " type=" + folder.type);
+ }
+
+ // Set swipe left/right folder
+ for (EntityFolder folder : folders)
+ if (EntityFolder.TRASH.equals(folder.type))
+ account.swipe_left = folder.id;
+ else if (EntityFolder.ARCHIVE.equals(folder.type))
+ account.swipe_right = folder.id;
+
+ db.account().updateAccount(account);
+
+ // Create identity
+ EntityIdentity identity = new EntityIdentity();
+ identity.name = displayName;
+ identity.email = user;
+ identity.account = account.id;
+
+ identity.host = "smtp-mail.outlook.com";
+ identity.starttls = true;
+ identity.port = 587;
+ identity.auth_type = MailService.AUTH_TYPE_OUTLOOK;
+ identity.user = user;
+ identity.password = password;
+ identity.synchronize = true;
+ identity.primary = true;
+
+ identity.id = db.identity().insertIdentity(identity);
+ args.putLong("identity", identity.id);
+ EntityLog.log(context, "Gmail identity=" + identity.name + " email=" + identity.email);
+
+ db.setTransactionSuccessful();
+ } finally {
+ db.endTransaction();
+ }
+ }
+
+ return null;
+ }
+
+ @Override
+ protected void onException(Bundle args, Throwable ex) {
+
+ }
+ }.execute(ActivitySetup.this, args, "outlook:account");
+ } catch (JSONException ex) {
+ Log.e(ex);
+ }
+ }
+
+ @Override
+ protected void onException(Bundle args, Throwable ex) {
+ Helper.unexpectedError(getSupportFragmentManager(), ex);
+ }
+ }.execute(ActivitySetup.this, args, "graph:profile");
+ }
+
+ @Override
+ public void onError(MsalException ex) {
+ Log.e(ex);
+ }
+
+ @Override
+ public void onCancel() {
+ Log.w("MSAL cancelled");
+ }
+ });
+ }
+
+ @Override
+ public void onError(MsalException ex) {
+ Log.e("MSAL", ex);
+ }
+ });
+ }
+
private void onViewQuickSetup(Intent intent) {
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
fragmentTransaction.replace(R.id.content_frame, new FragmentQuickSetup()).addToBackStack("quick");
@@ -1159,6 +1367,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_OUTLOOK.equals(action))
+ onOutlook(intent);
else if (ACTION_QUICK_SETUP.equals(action))
onViewQuickSetup(intent);
else if (ACTION_VIEW_ACCOUNTS.equals(action))
diff --git a/app/src/main/java/eu/faircode/email/FragmentSetup.java b/app/src/main/java/eu/faircode/email/FragmentSetup.java
index 3eb8fe51fd..307e542886 100644
--- a/app/src/main/java/eu/faircode/email/FragmentSetup.java
+++ b/app/src/main/java/eu/faircode/email/FragmentSetup.java
@@ -166,7 +166,8 @@ 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_other, 2, R.string.title_setup_other);
+ //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_other, 3, R.string.title_setup_other);
popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
@Override
@@ -179,6 +180,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_outlook:
+ lbm.sendBroadcast(new Intent(ActivitySetup.ACTION_QUICK_OUTLOOK));
+ return true;
case R.string.title_setup_other:
lbm.sendBroadcast(new Intent(ActivitySetup.ACTION_QUICK_SETUP));
return true;
diff --git a/app/src/main/java/eu/faircode/email/MailService.java b/app/src/main/java/eu/faircode/email/MailService.java
index a57b1629fc..c914e75c50 100644
--- a/app/src/main/java/eu/faircode/email/MailService.java
+++ b/app/src/main/java/eu/faircode/email/MailService.java
@@ -45,6 +45,7 @@ public class MailService implements AutoCloseable {
static final int AUTH_TYPE_PASSWORD = 1;
static final int AUTH_TYPE_GMAIL = 2;
+ static final int AUTH_TYPE_OUTLOOK = 3;
private final static int CHECK_TIMEOUT = 15 * 1000; // milliseconds
private final static int CONNECT_TIMEOUT = 20 * 1000; // milliseconds
@@ -175,7 +176,7 @@ public class MailService implements AutoCloseable {
public String connect(String host, int port, int auth, String user, String password) throws MessagingException {
try {
- if (auth == AUTH_TYPE_GMAIL)
+ if (auth == AUTH_TYPE_GMAIL || auth == AUTH_TYPE_OUTLOOK)
properties.put("mail." + protocol + ".auth.mechanisms", "XOAUTH2");
//if (BuildConfig.DEBUG)
diff --git a/app/src/main/res/raw/msal_config.json b/app/src/main/res/raw/msal_config.json
new file mode 100644
index 0000000000..2f739eafe8
--- /dev/null
+++ b/app/src/main/res/raw/msal_config.json
@@ -0,0 +1,14 @@
+{
+ "client_id": "3514cf2c-e7a3-45a2-80d4-6a3c3498eca0",
+ "authorization_user_agent": "DEFAULT",
+ "redirect_uri": "msauth://eu.faircode.email/F7oVwa9V2SX5i5nOpDddTN9MF0s%3D",
+ "authorities": [
+ {
+ "type": "AAD",
+ "audience": {
+ "type": "AzureADMultipleOrgs",
+ "tenant_id": "organizations"
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 7f8d3ba2ad..35a8b4967a 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -138,6 +138,7 @@
Wizard
Go \'back\' to go to the inbox
Gmail
+ Outlook
Other provider
Authorizing Google accounts will work in official versions only because Android checks the app signature
Please grant permissions to select an account and read your name