Added realm

This commit is contained in:
M66B 2019-01-10 18:24:20 +00:00
parent 821178754d
commit 373251f535
16 changed files with 1426 additions and 24 deletions

1
FAQ.md
View File

@ -972,7 +972,6 @@ The following authentication methods are supported and used in this order:
* LOGIN
* PLAIN
* DIGEST-MD5
* XOAUTH2 (used when an account was selected)
SASL authentication methods, like CRAM-MD5, are not supported

File diff suppressed because it is too large Load Diff

View File

@ -1281,7 +1281,7 @@ public class ActivityView extends ActivityBilling implements FragmentManager.OnB
} else {
// Decode message
Properties props = MessageHelper.getSessionProperties(Helper.AUTH_TYPE_PASSWORD, false);
Properties props = MessageHelper.getSessionProperties(Helper.AUTH_TYPE_PASSWORD, null, false);
Session isession = Session.getInstance(props, null);
ByteArrayInputStream is = new ByteArrayInputStream(decrypted.toByteArray());
MimeMessage imessage = new MimeMessage(isession, is);

View File

@ -49,7 +49,7 @@ import io.requery.android.database.sqlite.RequerySQLiteOpenHelperFactory;
// https://developer.android.com/topic/libraries/architecture/room.html
@Database(
version = 32,
version = 33,
entities = {
EntityIdentity.class,
EntityAccount.class,
@ -404,6 +404,14 @@ public abstract class DB extends RoomDatabase {
db.execSQL("CREATE INDEX `index_message_ui_snoozed` ON `message` (`ui_snoozed`)");
}
})
.addMigrations(new Migration(32, 33) {
@Override
public void migrate(SupportSQLiteDatabase db) {
Log.i("DB migration from version " + startVersion + " to " + endVersion);
db.execSQL("ALTER TABLE `account` ADD COLUMN `realm` TEXT");
db.execSQL("ALTER TABLE `identity` ADD COLUMN `realm` TEXT");
}
})
.build();
}

View File

@ -58,6 +58,7 @@ public class EntityAccount {
public String user;
@NonNull
public String password;
public String realm;
public String name;
public String signature; // obsolete

View File

@ -68,6 +68,7 @@ public class EntityIdentity {
public String user;
@NonNull
public String password;
public String realm;
@NonNull
public Boolean synchronize;
@NonNull

View File

@ -98,6 +98,7 @@ public class FragmentAccount extends FragmentEx {
private EditText etPort;
private EditText etUser;
private TextInputLayout tilPassword;
private EditText etRealm;
private EditText etName;
private Button btnColor;
@ -168,6 +169,7 @@ public class FragmentAccount extends FragmentEx {
cbInsecure = view.findViewById(R.id.cbInsecure);
etUser = view.findViewById(R.id.etUser);
tilPassword = view.findViewById(R.id.tilPassword);
etRealm = view.findViewById(R.id.etRealm);
etName = view.findViewById(R.id.etName);
btnColor = view.findViewById(R.id.btnColor);
@ -233,6 +235,7 @@ public class FragmentAccount extends FragmentEx {
etUser.setText(null);
tilPassword.getEditText().setText(null);
etRealm.setText(null);
etName.setText(position > 1 ? provider.name : null);
etPrefix.setText(provider.prefix);
@ -425,6 +428,7 @@ public class FragmentAccount extends FragmentEx {
args.putString("port", etPort.getText().toString());
args.putString("user", etUser.getText().toString());
args.putString("password", tilPassword.getEditText().getText().toString());
args.putString("realm", etRealm.getText().toString());
args.putInt("auth_type", authorized == null ? Helper.AUTH_TYPE_PASSWORD : provider.getAuthType());
new SimpleTask<CheckResult>() {
@ -457,6 +461,7 @@ public class FragmentAccount extends FragmentEx {
String port = args.getString("port");
String user = args.getString("user");
String password = args.getString("password");
String realm = args.getString("realm");
int auth_type = args.getInt("auth_type");
if (TextUtils.isEmpty(host))
@ -468,11 +473,14 @@ public class FragmentAccount extends FragmentEx {
if (TextUtils.isEmpty(password) && !insecure)
throw new IllegalArgumentException(context.getString(R.string.title_no_password));
if (TextUtils.isEmpty(realm))
realm = null;
CheckResult result = new CheckResult();
result.folders = new ArrayList<>();
// Check IMAP server / get folders
Properties props = MessageHelper.getSessionProperties(auth_type, insecure);
Properties props = MessageHelper.getSessionProperties(auth_type, realm, insecure);
Session isession = Session.getInstance(props, null);
isession.setDebug(true);
IMAPStore istore = null;
@ -651,6 +659,7 @@ public class FragmentAccount extends FragmentEx {
args.putString("port", etPort.getText().toString());
args.putString("user", etUser.getText().toString());
args.putString("password", tilPassword.getEditText().getText().toString());
args.putString("realm", etRealm.getText().toString());
args.putString("name", etName.getText().toString());
args.putInt("color", color);
@ -699,6 +708,7 @@ public class FragmentAccount extends FragmentEx {
String port = args.getString("port");
String user = args.getString("user");
String password = args.getString("password");
String realm = args.getString("realm");
String name = args.getString("name");
Integer color = args.getInt("color");
@ -729,6 +739,9 @@ public class FragmentAccount extends FragmentEx {
if (synchronize && drafts == null)
throw new IllegalArgumentException(context.getString(R.string.title_no_drafts));
if (TextUtils.isEmpty(realm))
realm = null;
if (Color.TRANSPARENT == color)
color = null;
if (TextUtils.isEmpty(prefix))
@ -742,7 +755,8 @@ public class FragmentAccount extends FragmentEx {
boolean check = (synchronize && (account == null ||
!host.equals(account.host) || Integer.parseInt(port) != account.port ||
!user.equals(account.user) || !password.equals(account.password)));
!user.equals(account.user) || !password.equals(account.password) ||
realm == null ? account.realm != null : !realm.equals(account.realm)));
boolean reload = (check || account == null ||
(account.prefix == null ? prefix != null : !account.prefix.equals(prefix)) ||
account.synchronize != synchronize ||
@ -750,7 +764,7 @@ public class FragmentAccount extends FragmentEx {
// Check IMAP server
if (check) {
Properties props = MessageHelper.getSessionProperties(auth_type, insecure);
Properties props = MessageHelper.getSessionProperties(auth_type, realm, insecure);
Session isession = Session.getInstance(props, null);
isession.setDebug(true);
@ -790,6 +804,7 @@ public class FragmentAccount extends FragmentEx {
account.port = Integer.parseInt(port);
account.user = user;
account.password = password;
account.realm = realm;
account.name = name;
account.color = color;
@ -1025,6 +1040,7 @@ public class FragmentAccount extends FragmentEx {
authorized = (account != null && account.auth_type != Helper.AUTH_TYPE_PASSWORD ? account.password : null);
etUser.setText(account == null ? null : account.user);
tilPassword.getEditText().setText(account == null ? null : account.password);
etRealm.setText(account == null ? null : account.realm);
etName.setText(account == null ? null : account.name);
etPrefix.setText(account == null ? null : account.prefix);
@ -1192,6 +1208,7 @@ public class FragmentAccount extends FragmentEx {
btnAuthorize.setEnabled(false);
etUser.setEnabled(false);
tilPassword.setEnabled(false);
etRealm.setEnabled(false);
btnCheck.setEnabled(false);
btnSave.setEnabled(false);
final Snackbar snackbar = Snackbar.make(view, R.string.title_authorizing, Snackbar.LENGTH_SHORT);
@ -1213,6 +1230,7 @@ public class FragmentAccount extends FragmentEx {
authorized = token;
etUser.setText(account.name);
tilPassword.getEditText().setText(token);
etRealm.setText(null);
} catch (Throwable ex) {
Log.e(ex);
if (ex instanceof OperationCanceledException ||
@ -1225,6 +1243,7 @@ public class FragmentAccount extends FragmentEx {
btnAuthorize.setEnabled(true);
etUser.setEnabled(true);
tilPassword.setEnabled(true);
etRealm.setEnabled(true);
btnCheck.setEnabled(true);
btnSave.setEnabled(true);
new Handler().postDelayed(new Runnable() {

View File

@ -900,7 +900,7 @@ public class FragmentCompose extends FragmentEx {
attachments.remove(attachment);
// Build message
Properties props = MessageHelper.getSessionProperties(Helper.AUTH_TYPE_PASSWORD, false);
Properties props = MessageHelper.getSessionProperties(Helper.AUTH_TYPE_PASSWORD, null, false);
Session isession = Session.getInstance(props, null);
MimeMessage imessage = new MimeMessage(isession);
MessageHelper.build(context, message, imessage);

View File

@ -101,6 +101,7 @@ public class FragmentIdentity extends FragmentEx {
private EditText etPort;
private EditText etUser;
private TextInputLayout tilPassword;
private EditText etRealm;
private CheckBox cbSynchronize;
private CheckBox cbPrimary;
@ -165,6 +166,7 @@ public class FragmentIdentity extends FragmentEx {
etPort = view.findViewById(R.id.etPort);
etUser = view.findViewById(R.id.etUser);
tilPassword = view.findViewById(R.id.tilPassword);
etRealm = view.findViewById(R.id.etRealm);
cbSynchronize = view.findViewById(R.id.cbSynchronize);
cbPrimary = view.findViewById(R.id.cbPrimary);
@ -226,12 +228,11 @@ public class FragmentIdentity extends FragmentEx {
grpAdvanced.setVisibility(View.VISIBLE);
}
// Copy account user name
// Copy account credentials
etEmail.setText(account.user);
etUser.setText(account.user);
// Copy account password
tilPassword.getEditText().setText(account.password);
etRealm.setText(account.realm);
setFolders(account.id);
}
@ -467,6 +468,7 @@ public class FragmentIdentity extends FragmentEx {
args.putString("port", etPort.getText().toString());
args.putString("user", etUser.getText().toString());
args.putString("password", tilPassword.getEditText().getText().toString());
args.putString("realm", etRealm.getText().toString());
args.putInt("color", color);
args.putString("signature", Html.toHtml(etSignature.getText()));
args.putBoolean("synchronize", cbSynchronize.isChecked());
@ -507,6 +509,7 @@ public class FragmentIdentity extends FragmentEx {
String port = args.getString("port");
String user = args.getString("user");
String password = args.getString("password");
String realm = args.getString("realm");
boolean synchronize = args.getBoolean("synchronize");
boolean primary = args.getBoolean("primary");
@ -536,6 +539,9 @@ public class FragmentIdentity extends FragmentEx {
if (TextUtils.isEmpty(display))
display = null;
if (TextUtils.isEmpty(realm))
realm = null;
if (TextUtils.isEmpty(replyto))
replyto = null;
else
@ -554,13 +560,14 @@ public class FragmentIdentity extends FragmentEx {
boolean check = (synchronize && (identity == null ||
!host.equals(identity.host) || Integer.parseInt(port) != identity.port ||
!user.equals(identity.user) || !password.equals(identity.password)));
!user.equals(identity.user) || !password.equals(identity.password) ||
realm == null ? identity.realm != null : !realm.equals(identity.realm)));
boolean reload = (identity == null || identity.synchronize != synchronize || check);
// Check SMTP server
if (check) {
String transportType = (starttls ? "smtp" : "smtps");
Properties props = MessageHelper.getSessionProperties(auth_type, insecure);
Properties props = MessageHelper.getSessionProperties(auth_type, realm, insecure);
Session isession = Session.getInstance(props, null);
isession.setDebug(true);
Transport itransport = isession.getTransport(transportType);
@ -599,6 +606,7 @@ public class FragmentIdentity extends FragmentEx {
identity.port = Integer.parseInt(port);
identity.user = user;
identity.password = password;
identity.realm = realm;
identity.synchronize = synchronize;
identity.primary = (identity.synchronize && primary);
@ -713,6 +721,7 @@ public class FragmentIdentity extends FragmentEx {
etPort.setText(identity == null ? null : Long.toString(identity.port));
etUser.setText(identity == null ? null : identity.user);
tilPassword.getEditText().setText(identity == null ? null : identity.password);
etRealm.setText(identity == null ? null : identity.realm);
cbSynchronize.setChecked(identity == null ? true : identity.synchronize);
cbPrimary.setChecked(identity == null ? true : identity.primary);

View File

@ -309,7 +309,7 @@ public class FragmentSetup extends FragmentEx {
folders.add(inbox);
{
Properties props = MessageHelper.getSessionProperties(auth_type, false);
Properties props = MessageHelper.getSessionProperties(auth_type, null, false);
Session isession = Session.getInstance(props, null);
isession.setDebug(true);
IMAPStore istore = null;
@ -364,7 +364,7 @@ public class FragmentSetup extends FragmentEx {
}
{
Properties props = MessageHelper.getSessionProperties(auth_type, false);
Properties props = MessageHelper.getSessionProperties(auth_type, null, false);
Session isession = Session.getInstance(props, null);
isession.setDebug(true);
Transport itransport = isession.getTransport(provider.smtp_starttls ? "smtp" : "smtps");

View File

@ -60,13 +60,12 @@ import javax.mail.internet.ParseException;
public class MessageHelper {
private MimeMessage imessage;
private String raw = null;
private final static int NETWORK_TIMEOUT = 60 * 1000; // milliseconds
private final static int FETCH_SIZE = 1024 * 1024; // bytes, default 16K
private final static int POOL_TIMEOUT = 3 * 60 * 1000; // milliseconds, default 45 sec
static Properties getSessionProperties(int auth_type, boolean insecure) {
static Properties getSessionProperties(int auth_type, String realm, boolean insecure) {
Properties props = new Properties();
props.put("mail.event.scope", "folder");
@ -78,6 +77,9 @@ public class MessageHelper {
props.put("mail.imaps.ssl.trust", "*");
props.put("mail.imaps.starttls.enable", "false");
if (realm != null)
props.put("mail.imaps.auth.ntlm.domain", realm);
// TODO: make timeouts configurable?
props.put("mail.imaps.connectiontimeout", Integer.toString(NETWORK_TIMEOUT));
props.put("mail.imaps.timeout", Integer.toString(NETWORK_TIMEOUT));
@ -100,7 +102,9 @@ public class MessageHelper {
props.put("mail.imap.ssl.trust", "*");
props.put("mail.imap.starttls.enable", "true");
props.put("mail.imap.starttls.required", "true");
props.put("mail.imap.auth", "true");
if (realm != null)
props.put("mail.imap.auth.ntlm.domain", realm);
props.put("mail.imap.connectiontimeout", Integer.toString(NETWORK_TIMEOUT));
props.put("mail.imap.timeout", Integer.toString(NETWORK_TIMEOUT));
@ -120,7 +124,10 @@ public class MessageHelper {
props.put("mail.smtps.ssl.trust", "*");
props.put("mail.smtps.starttls.enable", "false");
props.put("mail.smtps.starttls.required", "false");
props.put("mail.smtps.auth", "true");
if (realm != null)
props.put("mail.smtps.auth.ntlm.domain", realm);
props.put("mail.smtps.connectiontimeout", Integer.toString(NETWORK_TIMEOUT));
props.put("mail.smtps.writetimeout", Integer.toString(NETWORK_TIMEOUT)); // one thread overhead
@ -130,7 +137,10 @@ public class MessageHelper {
props.put("mail.smtp.ssl.trust", "*");
props.put("mail.smtp.starttls.enable", "true");
props.put("mail.smtp.starttls.required", "true");
props.put("mail.smtp.auth", "true");
if (realm != null)
props.put("mail.smtp.auth.ntlm.domain", realm);
props.put("mail.smtp.connectiontimeout", Integer.toString(NETWORK_TIMEOUT));
props.put("mail.smtp.writetimeout", Integer.toString(NETWORK_TIMEOUT)); // one thread overhead

View File

@ -841,7 +841,7 @@ public class ServiceSynchronize extends LifecycleService {
System.setProperty("mail.socket.debug", Boolean.toString(debug));
// Create session
Properties props = MessageHelper.getSessionProperties(account.auth_type, account.insecure);
Properties props = MessageHelper.getSessionProperties(account.auth_type, account.realm, account.insecure);
final Session isession = Session.getInstance(props, null);
isession.setDebug(debug);
// adb -t 1 logcat | grep "fairemail\|System.out"
@ -1750,7 +1750,7 @@ public class ServiceSynchronize extends LifecycleService {
String transportType = (ident.starttls ? "smtp" : "smtps");
// Get properties
Properties props = MessageHelper.getSessionProperties(ident.auth_type, ident.insecure);
Properties props = MessageHelper.getSessionProperties(ident.auth_type, ident.realm, ident.insecure);
props.put("mail.smtp.localhost", ident.host);
// Create session

View File

@ -154,7 +154,7 @@ public class ViewModelBrowse extends ViewModel {
EntityAccount account = db.account().getAccount(folder.account);
try {
Properties props = MessageHelper.getSessionProperties(account.auth_type, account.insecure);
Properties props = MessageHelper.getSessionProperties(account.auth_type, account.realm, account.insecure);
Session isession = Session.getInstance(props, null);
Log.i("Boundary connecting account=" + account.name);

View File

@ -220,6 +220,26 @@
android:textAppearance="@style/TextAppearance.AppCompat.Medium" />
</com.google.android.material.textfield.TextInputLayout>
<TextView
android:id="@+id/tvRealm"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:text="@string/title_realm"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tilPassword" />
<EditText
android:id="@+id/etRealm"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/title_optional"
android:inputType="text"
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tvRealm" />
<!-- name -->
<TextView
@ -230,7 +250,7 @@
android:text="@string/title_account_name"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tilPassword" />
app:layout_constraintTop_toBottomOf="@id/etRealm" />
<TextView
android:id="@+id/tvNameRemark"
@ -594,7 +614,7 @@
android:id="@+id/grpAuthorize"
android:layout_width="0dp"
android:layout_height="0dp"
app:constraint_referenced_ids="tvUser,etUser,tvPassword,tilPassword,tvName,tvNameRemark,etName,btnColor,vwColor,ibColorDefault" />
app:constraint_referenced_ids="tvUser,etUser,tvPassword,tilPassword,tvRealm,etRealm,tvName,tvNameRemark,etName,btnColor,vwColor,ibColorDefault" />
<androidx.constraintlayout.widget.Group
android:id="@+id/grpAdvanced"

View File

@ -373,6 +373,26 @@
android:textAppearance="@style/TextAppearance.AppCompat.Medium" />
</com.google.android.material.textfield.TextInputLayout>
<TextView
android:id="@+id/tvRealm"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:text="@string/title_realm"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tilPassword" />
<EditText
android:id="@+id/etRealm"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/title_optional"
android:inputType="text"
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tvRealm" />
<CheckBox
android:id="@+id/cbSynchronize"
android:layout_width="wrap_content"
@ -380,7 +400,7 @@
android:layout_marginTop="12dp"
android:text="@string/title_synchronize_identity"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tilPassword" />
app:layout_constraintTop_toBottomOf="@id/etRealm" />
<CheckBox
android:id="@+id/cbPrimary"
@ -546,6 +566,6 @@
android:id="@+id/grpAdvanced"
android:layout_width="0dp"
android:layout_height="0dp"
app:constraint_referenced_ids="tvReplyTo,etReplyTo,tvBcc,etBcc,cbDeliveryReceipt,cbReadReceipt,tvReceipt,tvProvider,spProvider,tvDomain,tvDomainHint,etDomain,btnAutoConfig,tvSmtp,tvHost,etHost,cbStartTls,cbInsecure,tvPort,etPort,tvUser,etUser,tvPassword,tilPassword,cbSynchronize,cbPrimary,tvSent,spSent,tvSentHint" />
app:constraint_referenced_ids="tvReplyTo,etReplyTo,tvBcc,etBcc,cbDeliveryReceipt,cbReadReceipt,tvReceipt,tvProvider,spProvider,tvDomain,tvDomainHint,etDomain,btnAutoConfig,tvSmtp,tvHost,etHost,cbStartTls,cbInsecure,tvPort,etPort,tvUser,etUser,tvPassword,tilPassword,tvRealm,etRealm,cbSynchronize,cbPrimary,tvSent,spSent,tvSentHint" />
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>

View File

@ -183,6 +183,7 @@
<string name="title_port">Port number</string>
<string name="title_user">User name</string>
<string name="title_password">Password</string>
<string name="title_realm">Realm</string>
<string name="title_authorize">Select account</string>
<string name="title_authorizing">Authorizing account &#8230;</string>
<string name="title_setup_advanced">Advanced</string>