Prepared OAuth POP3

This commit is contained in:
M66B 2022-04-17 11:44:44 +02:00
parent e2febdf1d4
commit 42f700fa63
8 changed files with 126 additions and 21 deletions

View File

@ -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);

View File

@ -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) &&

View File

@ -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<TupleAccountSwipes> 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<List<EntityFolder>>() {

View File

@ -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<Void>() {
@ -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<EntityFolder> 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);

View File

@ -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<EntityAccount>() {
@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());

View File

@ -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);

View File

@ -90,6 +90,16 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/etTenant" />
<CheckBox
android:id="@+id/cbPop"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:text="@string/title_pop3"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tvTenantHint" />
<CheckBox
android:id="@+id/cbUpdate"
android:layout_width="wrap_content"
@ -98,7 +108,7 @@
android:text="@string/title_setup_oauth_update"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tvTenantHint" />
app:layout_constraintTop_toBottomOf="@id/cbPop" />
<Button
android:id="@+id/btnOAuth"

View File

@ -16,6 +16,10 @@
host="smtp.gmail.com"
port="465"
starttls="false" />
<!--pop
host="pop.gmail.com"
port="995"
starttls="false" /-->
<oauth
askAccount="true"
authorizationEndpoint="https://accounts.google.com/o/oauth2/auth"
@ -112,6 +116,11 @@
host="smtp.office365.com"
port="587"
starttls="true" />
<!--pop
host="outlook.office365.com"
port="995"
starttls="false" /-->
<!-- https://outlook.office.com/POP.AccessAsUser.All -->
<oauth
askAccount="true"
authorizationEndpoint="https://login.microsoftonline.com/{tenant}/oauth2/v2.0/authorize"
@ -143,6 +152,11 @@
host="smtp.office365.com"
port="587"
starttls="true" />
<!--pop
host="outlook.office365.com"
port="995"
starttls="false" /-->
<!-- https://outlook.office.com/POP.AccessAsUser.All -->
<oauth
askAccount="true"
authorizationEndpoint="https://login.microsoftonline.com/{tenant}/oauth2/v2.0/authorize"
@ -202,6 +216,10 @@
host="smtp.mail.yahoo.com"
port="465"
starttls="false" />
<!--pop
host="pop.mail.yahoo.com"
port="995"
starttls="false" /-->
<oauth
askAccount="true"
authorizationEndpoint="https://api.login.yahoo.com/oauth2/request_auth"
@ -400,6 +418,10 @@
host="smtp.yandex.com"
port="465"
starttls="false" />
<!--pop
host="pop.yandex.ru"
port="995"
starttls="false" /-->
<oauth
askAccount="true"
authorizationEndpoint="https://oauth.yandex.com/authorize"
@ -599,6 +621,10 @@
host="smtp.mail.ru"
port="465"
starttls="false" />
<!--pop
host="pop.mail.ru"
port="995"
starttls="false" /-->
<oauth
askAccount="false"
authorizationEndpoint="https://oauth.mail.ru/login"