From 42f700fa6340cfce00ab82d8e4ba49c11c858d1e Mon Sep 17 00:00:00 2001 From: M66B Date: Sun, 17 Apr 2022 11:44:44 +0200 Subject: [PATCH] Prepared OAuth POP3 --- .../java/eu/faircode/email/EmailProvider.java | 6 +++ .../java/eu/faircode/email/EmailService.java | 3 ++ .../faircode/email/FragmentDialogAccount.java | 27 ++++++++-- .../java/eu/faircode/email/FragmentOAuth.java | 52 ++++++++++++++----- .../java/eu/faircode/email/FragmentPop.java | 18 +++++-- .../java/eu/faircode/email/FragmentSetup.java | 3 +- app/src/main/res/layout/fragment_oauth.xml | 12 ++++- app/src/main/res/xml/providers.xml | 26 ++++++++++ 8 files changed, 126 insertions(+), 21 deletions(-) diff --git a/app/src/main/java/eu/faircode/email/EmailProvider.java b/app/src/main/java/eu/faircode/email/EmailProvider.java index f054e4527b..1478acbb23 100644 --- a/app/src/main/java/eu/faircode/email/EmailProvider.java +++ b/app/src/main/java/eu/faircode/email/EmailProvider.java @@ -90,6 +90,7 @@ public class EmailProvider implements Parcelable { public String link; public Server imap = new Server(); public Server smtp = new Server(); + public Server pop; public OAuth oauth; public UserType user = UserType.EMAIL; public String username; @@ -267,6 +268,11 @@ public class EmailProvider implements Parcelable { provider.smtp.host = xml.getAttributeValue(null, "host"); provider.smtp.port = getAttributeIntValue(xml, "port", 0); provider.smtp.starttls = getAttributeBooleanValue(xml, "starttls", false); + } else if ("pop".equals(name)) { + provider.pop = new Server(); + provider.pop.host = xml.getAttributeValue(null, "host"); + provider.pop.port = getAttributeIntValue(xml, "port", 0); + provider.pop.starttls = getAttributeBooleanValue(xml, "starttls", false); } else if ("oauth".equals(name)) { provider.oauth = new OAuth(); provider.oauth.enabled = getAttributeBooleanValue(xml, "enabled", false); diff --git a/app/src/main/java/eu/faircode/email/EmailService.java b/app/src/main/java/eu/faircode/email/EmailService.java index 4b1bf62d67..1238923756 100644 --- a/app/src/main/java/eu/faircode/email/EmailService.java +++ b/app/src/main/java/eu/faircode/email/EmailService.java @@ -441,6 +441,9 @@ public class EmailService implements AutoCloseable { if (auth == AUTH_TYPE_OAUTH && "imap.mail.yahoo.com".equals(host)) properties.put("mail." + protocol + ".yahoo.guid", "FAIRMAIL_V1"); + if (auth == AUTH_TYPE_OAUTH && "pop3s".equals(protocol) && "outlook.office365.com".equals(host)) + properties.put("mail." + protocol + ".auth.xoauth2.two.line.authentication.format", "true"); + connect(host, port, auth, user, factory); } catch (AuthenticationFailedException ex) { //if ("outlook.office365.com".equals(host) && diff --git a/app/src/main/java/eu/faircode/email/FragmentDialogAccount.java b/app/src/main/java/eu/faircode/email/FragmentDialogAccount.java index ff5cca6e3c..cea9768931 100644 --- a/app/src/main/java/eu/faircode/email/FragmentDialogAccount.java +++ b/app/src/main/java/eu/faircode/email/FragmentDialogAccount.java @@ -91,6 +91,7 @@ public class FragmentDialogAccount extends FragmentDialogBase { Bundle args = getArguments(); final long account = args.getLong("account"); + final boolean pop = args.getBoolean("pop"); ibEditName.setOnClickListener(new View.OnClickListener() { @Override @@ -141,7 +142,7 @@ public class FragmentDialogAccount extends FragmentDialogBase { lbm.sendBroadcast( new Intent(ActivitySetup.ACTION_EDIT_ACCOUNT) .putExtra("id", account) - .putExtra("protocol", EntityAccount.TYPE_IMAP)); + .putExtra("protocol", pop ? EntityAccount.TYPE_POP : EntityAccount.TYPE_IMAP)); dismiss(); } }); @@ -174,8 +175,18 @@ public class FragmentDialogAccount extends FragmentDialogBase { @Override public void onChanged(List swipes) { if (swipes != null && swipes.size() == 1) { - String left = swipes.get(0).left_name; - String right = swipes.get(0).right_name; + String left; + if (swipes.get(0).swipe_left != null && swipes.get(0).swipe_left < 0) + left = getSwipeTitle(context, (long) swipes.get(0).swipe_left); + else + left = swipes.get(0).left_name; + + String right; + if (swipes.get(0).swipe_right != null && swipes.get(0).swipe_right < 0) + right = getSwipeTitle(context, (long) swipes.get(0).swipe_right); + else + right = swipes.get(0).right_name; + tvLeft.setText(left == null ? "-" : left); tvRight.setText(right == null ? "-" : right); } else { @@ -183,6 +194,16 @@ public class FragmentDialogAccount extends FragmentDialogBase { tvRight.setText("?"); } } + + private String getSwipeTitle(Context context, long type) { + if (type == EntityMessage.SWIPE_ACTION_SEEN) + return context.getString(R.string.title_seen); + + if (type == EntityMessage.SWIPE_ACTION_DELETE) + return context.getString(R.string.title_delete_permanently); + + return "???"; + } }); db.folder().liveSystemFolders(account).observe(this, new Observer>() { diff --git a/app/src/main/java/eu/faircode/email/FragmentOAuth.java b/app/src/main/java/eu/faircode/email/FragmentOAuth.java index 1cc62f5a12..5c399f8f44 100644 --- a/app/src/main/java/eu/faircode/email/FragmentOAuth.java +++ b/app/src/main/java/eu/faircode/email/FragmentOAuth.java @@ -101,6 +101,7 @@ public class FragmentOAuth extends FragmentBase { private String personal; private String address; + private boolean pop; private boolean update; private ViewGroup view; @@ -111,6 +112,7 @@ public class FragmentOAuth extends FragmentBase { private EditText etName; private EditText etEmail; private EditText etTenant; + private CheckBox cbPop; private CheckBox cbUpdate; private Button btnOAuth; private ContentLoadingProgressBar pbOAuth; @@ -141,6 +143,7 @@ public class FragmentOAuth extends FragmentBase { personal = args.getString("personal"); address = args.getString("address"); + pop = args.getBoolean("pop", false); update = args.getBoolean("update", true); } @@ -159,6 +162,7 @@ public class FragmentOAuth extends FragmentBase { etName = view.findViewById(R.id.etName); etEmail = view.findViewById(R.id.etEmail); etTenant = view.findViewById(R.id.etTenant); + cbPop = view.findViewById(R.id.cbPop); cbUpdate = view.findViewById(R.id.cbUpdate); btnOAuth = view.findViewById(R.id.btnOAuth); pbOAuth = view.findViewById(R.id.pbOAuth); @@ -216,6 +220,7 @@ public class FragmentOAuth extends FragmentBase { etName.setVisibility(askAccount ? View.VISIBLE : View.GONE); etEmail.setVisibility(askAccount ? View.VISIBLE : View.GONE); grpTenant.setVisibility(askTenant ? View.VISIBLE : View.GONE); + cbPop.setVisibility(pop ? View.VISIBLE : View.GONE); pbOAuth.setVisibility(View.GONE); tvConfiguring.setVisibility(View.GONE); tvGmailHint.setVisibility("gmail".equals(id) ? View.VISIBLE : View.GONE); @@ -224,6 +229,7 @@ public class FragmentOAuth extends FragmentBase { etName.setText(personal); etEmail.setText(address); etTenant.setText(null); + cbPop.setChecked(false); cbUpdate.setChecked(update); return view; @@ -278,6 +284,7 @@ public class FragmentOAuth extends FragmentBase { etName.setEnabled(false); etEmail.setEnabled(false); etTenant.setEnabled(false); + cbPop.setEnabled(false); cbUpdate.setEnabled(false); btnOAuth.setEnabled(false); pbOAuth.setVisibility(View.VISIBLE); @@ -419,6 +426,7 @@ public class FragmentOAuth extends FragmentBase { etName.setEnabled(true); etEmail.setEnabled(true); etTenant.setEnabled(true); + cbPop.setEnabled(true); cbUpdate.setEnabled(true); AuthorizationResponse auth = AuthorizationResponse.fromIntent(data); @@ -505,6 +513,7 @@ public class FragmentOAuth extends FragmentBase { args.putBoolean("askAccount", askAccount); args.putString("personal", etName.getText().toString().trim()); args.putString("address", etEmail.getText().toString().trim()); + args.putBoolean("pop", cbPop.isChecked()); args.putBoolean("update", cbUpdate.isChecked()); new SimpleTask() { @@ -528,10 +537,14 @@ public class FragmentOAuth extends FragmentBase { boolean askAccount = args.getBoolean("askAccount", false); String personal = args.getString("personal"); String address = args.getString("address"); + boolean pop = args.getBoolean("pop"); EmailProvider provider = EmailProvider.getProvider(context, id); - String aprotocol = (provider.imap.starttls ? "imap" : "imaps"); - int aencryption = (provider.imap.starttls ? EmailService.ENCRYPTION_STARTTLS : EmailService.ENCRYPTION_SSL); + if (provider.pop == null) + pop = false; + EmailProvider.Server inbound = (pop ? provider.pop : provider.imap); + String aprotocol = (pop ? (inbound.starttls ? "pop3" : "pop3s") : (inbound.starttls ? "imap" : "imaps")); + int aencryption = (inbound.starttls ? EmailService.ENCRYPTION_STARTTLS : EmailService.ENCRYPTION_SSL); String iprotocol = (provider.smtp.starttls ? "smtp" : "smtps"); int iencryption = (provider.smtp.starttls ? EmailService.ENCRYPTION_STARTTLS : EmailService.ENCRYPTION_SSL); @@ -694,7 +707,7 @@ public class FragmentOAuth extends FragmentBase { context, aprotocol, null, aencryption, false, EmailService.PURPOSE_CHECK, true)) { aservice.connect( - provider.imap.host, provider.imap.port, + inbound.host, inbound.port, AUTH_TYPE_OAUTH, provider.id, alt, state, null, null); @@ -759,17 +772,20 @@ public class FragmentOAuth extends FragmentBase { List folders; - Log.i("OAuth checking IMAP provider=" + provider.id); + Log.i("OAuth checking IMAP/POP3 provider=" + provider.id); try (EmailService aservice = new EmailService( context, aprotocol, null, aencryption, false, EmailService.PURPOSE_CHECK, true)) { aservice.connect( - provider.imap.host, provider.imap.port, + inbound.host, inbound.port, AUTH_TYPE_OAUTH, provider.id, sharedname == null ? username : sharedname, state, null, null); - folders = aservice.getFolders(); + if (pop) + folders = EntityFolder.getPopFolders(context); + else + folders = aservice.getFolders(); } Log.i("OAuth checking SMTP provider=" + provider.id); @@ -806,9 +822,10 @@ public class FragmentOAuth extends FragmentBase { // Create account EntityAccount account = new EntityAccount(); - account.host = provider.imap.host; + account.protocol = (pop ? EntityAccount.TYPE_POP : EntityAccount.TYPE_IMAP); + account.host = inbound.host; account.encryption = aencryption; - account.port = provider.imap.port; + account.port = inbound.port; account.auth_type = AUTH_TYPE_OAUTH; account.provider = provider.id; account.user = (sharedname == null ? username : sharedname); @@ -848,11 +865,16 @@ public class FragmentOAuth extends FragmentBase { } // 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; + if (pop) { + account.swipe_left = EntityMessage.SWIPE_ACTION_DELETE; + account.swipe_right = EntityMessage.SWIPE_ACTION_SEEN; + } else { + 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); @@ -878,6 +900,8 @@ public class FragmentOAuth extends FragmentBase { ident.id = db.identity().insertIdentity(ident); EntityLog.log(context, "OAuth identity=" + ident.name + " email=" + ident.email); } + + args.putBoolean("pop", pop); } else { args.putLong("account", update.id); EntityLog.log(context, "OAuth update account=" + update.name); @@ -928,6 +952,7 @@ public class FragmentOAuth extends FragmentBase { etName.setEnabled(true); etEmail.setEnabled(true); etTenant.setEnabled(true); + cbPop.setEnabled(true); cbUpdate.setEnabled(true); btnOAuth.setEnabled(true); pbOAuth.setVisibility(View.GONE); @@ -967,6 +992,7 @@ public class FragmentOAuth extends FragmentBase { etName.setEnabled(true); etEmail.setEnabled(true); etTenant.setEnabled(true); + cbPop.setEnabled(true); cbUpdate.setEnabled(true); btnOAuth.setEnabled(true); pbOAuth.setVisibility(View.GONE); diff --git a/app/src/main/java/eu/faircode/email/FragmentPop.java b/app/src/main/java/eu/faircode/email/FragmentPop.java index 5fc35c1050..4ab9b60cc6 100644 --- a/app/src/main/java/eu/faircode/email/FragmentPop.java +++ b/app/src/main/java/eu/faircode/email/FragmentPop.java @@ -108,6 +108,7 @@ public class FragmentPop extends FragmentBase { private ContentLoadingProgressBar pbWait; private long id = -1; + private int auth = AUTH_TYPE_PASSWORD; private boolean saving = false; private static final int REQUEST_COLOR = 1; @@ -303,6 +304,7 @@ public class FragmentPop extends FragmentBase { args.putInt("encryption", encryption); args.putBoolean("insecure", cbInsecure.isChecked()); args.putString("port", etPort.getText().toString()); + args.putInt("auth", auth); args.putString("user", etUser.getText().toString()); args.putString("password", tilPassword.getEditText().getText().toString()); @@ -353,6 +355,7 @@ public class FragmentPop extends FragmentBase { int encryption = args.getInt("encryption"); boolean insecure = args.getBoolean("insecure"); String port = args.getString("port"); + int auth = args.getInt("auth"); String user = args.getString("user").trim(); String password = args.getString("password"); @@ -483,7 +486,7 @@ public class FragmentPop extends FragmentBase { EmailService.PURPOSE_CHECK, true)) { iservice.connect( host, Integer.parseInt(port), - AUTH_TYPE_PASSWORD, null, + auth, null, user, password, null, null); } @@ -495,7 +498,7 @@ public class FragmentPop extends FragmentBase { if (account != null && !account.password.equals(password)) { String domain = UriHelper.getParentDomain(context, account.host); String match = (Objects.equals(account.host, domain) ? account.host : "%." + domain); - int count = db.identity().setIdentityPassword(account.id, account.user, password, AUTH_TYPE_PASSWORD, match); + int count = db.identity().setIdentityPassword(account.id, account.user, password, auth, match); Log.i("Updated passwords=" + count + " match=" + match); } @@ -508,7 +511,7 @@ public class FragmentPop extends FragmentBase { account.encryption = encryption; account.insecure = insecure; account.port = Integer.parseInt(port); - account.auth_type = AUTH_TYPE_PASSWORD; + account.auth_type = auth; account.user = user; account.password = password; @@ -647,6 +650,7 @@ public class FragmentPop extends FragmentBase { @Override public void onSaveInstanceState(Bundle outState) { outState.putString("fair:password", tilPassword.getEditText().getText().toString()); + outState.putInt("fair:auth", auth); super.onSaveInstanceState(outState); } @@ -726,6 +730,8 @@ public class FragmentPop extends FragmentBase { spRight.setSelection(pos); } + auth = (account == null ? AUTH_TYPE_PASSWORD : account.auth_type); + new SimpleTask() { @Override protected EntityAccount onExecute(Context context, Bundle args) { @@ -745,10 +751,16 @@ public class FragmentPop extends FragmentBase { }.execute(FragmentPop.this, new Bundle(), "account:primary"); } else { tilPassword.getEditText().setText(savedInstanceState.getString("fair:password")); + auth = savedInstanceState.getInt("fair:auth"); } Helper.setViewsEnabled(view, true); + if (auth != AUTH_TYPE_PASSWORD) { + etUser.setEnabled(false); + tilPassword.setEnabled(false); + } + cbOnDemand.setEnabled(cbSynchronize.isChecked()); cbPrimary.setEnabled(cbSynchronize.isChecked()); diff --git a/app/src/main/java/eu/faircode/email/FragmentSetup.java b/app/src/main/java/eu/faircode/email/FragmentSetup.java index 25824d08f0..ee5a41b0c1 100644 --- a/app/src/main/java/eu/faircode/email/FragmentSetup.java +++ b/app/src/main/java/eu/faircode/email/FragmentSetup.java @@ -269,7 +269,8 @@ public class FragmentSetup extends FragmentBase { .putExtra("name", provider.description) .putExtra("privacy", provider.oauth.privacy) .putExtra("askAccount", provider.oauth.askAccount) - .putExtra("askTenant", provider.oauth.askTenant())); + .putExtra("askTenant", provider.oauth.askTenant()) + .putExtra("pop", provider.pop != null)); resid = res.getIdentifier("provider_" + provider.id, "drawable", pkg); if (resid != 0) item.setIcon(resid); diff --git a/app/src/main/res/layout/fragment_oauth.xml b/app/src/main/res/layout/fragment_oauth.xml index 3eda08c1da..b487a11923 100644 --- a/app/src/main/res/layout/fragment_oauth.xml +++ b/app/src/main/res/layout/fragment_oauth.xml @@ -90,6 +90,16 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/etTenant" /> + + + app:layout_constraintTop_toBottomOf="@id/cbPop" />