mirror of
https://github.com/etesync/android
synced 2024-11-22 16:08:13 +00:00
Improve HTTP authentication
* use preemptive Basic auth automatically for HTTPS connections * cache auth parameters (Basic/Digest)
This commit is contained in:
parent
ae145d897e
commit
bab84d7d0f
@ -53,11 +53,11 @@ public class DavResourceFinderTest extends InstrumentationTestCase {
|
|||||||
server.setDispatcher(new TestDispatcher());
|
server.setDispatcher(new TestDispatcher());
|
||||||
server.start();
|
server.start();
|
||||||
|
|
||||||
credentials = new LoginCredentials(URI.create("/"), "mock", "12345", true);
|
credentials = new LoginCredentials(URI.create("/"), "mock", "12345");
|
||||||
finder = new DavResourceFinder(getInstrumentation().getContext(), credentials);
|
finder = new DavResourceFinder(getInstrumentation().getContext(), credentials);
|
||||||
|
|
||||||
client = HttpClient.create();
|
client = HttpClient.create();
|
||||||
client = HttpClient.addAuthentication(client, credentials.userName, credentials.password, credentials.authPreemptive);
|
client = HttpClient.addAuthentication(client, credentials.userName, credentials.password);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -129,8 +129,11 @@ public class DavResourceFinderTest extends InstrumentationTestCase {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public MockResponse dispatch(RecordedRequest rq) throws InterruptedException {
|
public MockResponse dispatch(RecordedRequest rq) throws InterruptedException {
|
||||||
if (!checkAuth(rq))
|
if (!checkAuth(rq)) {
|
||||||
return new MockResponse().setResponseCode(401);
|
MockResponse authenticate = new MockResponse().setResponseCode(401);
|
||||||
|
authenticate.setHeader("WWW-Authenticate", "Basic realm=\"test\"");
|
||||||
|
return authenticate;
|
||||||
|
}
|
||||||
|
|
||||||
String path = rq.getPath();
|
String path = rq.getPath();
|
||||||
|
|
||||||
|
@ -60,7 +60,6 @@ public class AccountSettings {
|
|||||||
KEY_SETTINGS_VERSION = "version",
|
KEY_SETTINGS_VERSION = "version",
|
||||||
|
|
||||||
KEY_USERNAME = "user_name",
|
KEY_USERNAME = "user_name",
|
||||||
KEY_AUTH_PREEMPTIVE = "auth_preemptive",
|
|
||||||
|
|
||||||
KEY_WIFI_ONLY = "wifi_only", // sync on WiFi only (default: false)
|
KEY_WIFI_ONLY = "wifi_only", // sync on WiFi only (default: false)
|
||||||
KEY_WIFI_ONLY_SSID = "wifi_only_ssid"; // restrict sync to specific WiFi SSID
|
KEY_WIFI_ONLY_SSID = "wifi_only_ssid"; // restrict sync to specific WiFi SSID
|
||||||
@ -147,11 +146,10 @@ public class AccountSettings {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Bundle initialUserData(String userName, boolean preemptive) {
|
public static Bundle initialUserData(String userName) {
|
||||||
Bundle bundle = new Bundle();
|
Bundle bundle = new Bundle();
|
||||||
bundle.putString(KEY_SETTINGS_VERSION, String.valueOf(CURRENT_VERSION));
|
bundle.putString(KEY_SETTINGS_VERSION, String.valueOf(CURRENT_VERSION));
|
||||||
bundle.putString(KEY_USERNAME, userName);
|
bundle.putString(KEY_USERNAME, userName);
|
||||||
bundle.putString(KEY_AUTH_PREEMPTIVE, Boolean.toString(preemptive));
|
|
||||||
return bundle;
|
return bundle;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -164,9 +162,6 @@ public class AccountSettings {
|
|||||||
public String password() { return accountManager.getPassword(account); }
|
public String password() { return accountManager.getPassword(account); }
|
||||||
public void password(@NonNull String password) { accountManager.setPassword(account, password); }
|
public void password(@NonNull String password) { accountManager.setPassword(account, password); }
|
||||||
|
|
||||||
public boolean preemptiveAuth() { return Boolean.parseBoolean(accountManager.getUserData(account, KEY_AUTH_PREEMPTIVE)); }
|
|
||||||
public void preemptiveAuth(boolean preemptive) { accountManager.setUserData(account, KEY_AUTH_PREEMPTIVE, Boolean.toString(preemptive)); }
|
|
||||||
|
|
||||||
|
|
||||||
// sync. settings
|
// sync. settings
|
||||||
|
|
||||||
|
@ -12,6 +12,7 @@ import android.accounts.Account;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
@ -21,9 +22,7 @@ import java.util.concurrent.TimeUnit;
|
|||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import at.bitfire.dav4android.BasicDigestAuthenticator;
|
import at.bitfire.dav4android.BasicDigestAuthHandler;
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import okhttp3.Credentials;
|
|
||||||
import okhttp3.Interceptor;
|
import okhttp3.Interceptor;
|
||||||
import okhttp3.OkHttpClient;
|
import okhttp3.OkHttpClient;
|
||||||
import okhttp3.Request;
|
import okhttp3.Request;
|
||||||
@ -46,13 +45,9 @@ public class HttpClient {
|
|||||||
public static OkHttpClient create(@NonNull Context context, @NonNull Account account, @NonNull final Logger logger) throws InvalidAccountException {
|
public static OkHttpClient create(@NonNull Context context, @NonNull Account account, @NonNull final Logger logger) throws InvalidAccountException {
|
||||||
OkHttpClient.Builder builder = defaultBuilder(logger);
|
OkHttpClient.Builder builder = defaultBuilder(logger);
|
||||||
|
|
||||||
// use account settings for authentication and logging
|
// use account settings for authentication
|
||||||
AccountSettings settings = new AccountSettings(context, account);
|
AccountSettings settings = new AccountSettings(context, account);
|
||||||
|
builder = addAuthentication(builder, null, settings.username(), settings.password());
|
||||||
if (settings.preemptiveAuth())
|
|
||||||
builder.addNetworkInterceptor(new PreemptiveAuthenticationInterceptor(settings.username(), settings.password()));
|
|
||||||
else
|
|
||||||
builder.authenticator(new BasicDigestAuthenticator(null, settings.username(), settings.password()));
|
|
||||||
|
|
||||||
return builder.build();
|
return builder.build();
|
||||||
}
|
}
|
||||||
@ -69,6 +64,7 @@ public class HttpClient {
|
|||||||
return create(App.log);
|
return create(App.log);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private static OkHttpClient.Builder defaultBuilder(@NonNull final Logger logger) {
|
private static OkHttpClient.Builder defaultBuilder(@NonNull final Logger logger) {
|
||||||
OkHttpClient.Builder builder = client.newBuilder();
|
OkHttpClient.Builder builder = client.newBuilder();
|
||||||
|
|
||||||
@ -107,24 +103,23 @@ public class HttpClient {
|
|||||||
return builder;
|
return builder;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static OkHttpClient.Builder addAuthentication(@NonNull OkHttpClient.Builder builder, @NonNull String username, @NonNull String password, boolean preemptive) {
|
private static OkHttpClient.Builder addAuthentication(@NonNull OkHttpClient.Builder builder, @Nullable String host, @NonNull String username, @NonNull String password) {
|
||||||
if (preemptive)
|
BasicDigestAuthHandler authHandler = new BasicDigestAuthHandler(host, username, password);
|
||||||
builder.addNetworkInterceptor(new PreemptiveAuthenticationInterceptor(username, password));
|
return builder
|
||||||
else
|
.addNetworkInterceptor(authHandler)
|
||||||
builder.authenticator(new BasicDigestAuthenticator(null, username, password));
|
.authenticator(authHandler);
|
||||||
return builder;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static OkHttpClient addAuthentication(@NonNull OkHttpClient client, @NonNull String username, @NonNull String password, boolean preemptive) {
|
public static OkHttpClient addAuthentication(@NonNull OkHttpClient client, @NonNull String username, @NonNull String password) {
|
||||||
OkHttpClient.Builder builder = client.newBuilder();
|
OkHttpClient.Builder builder = client.newBuilder();
|
||||||
addAuthentication(builder, username, password, preemptive);
|
addAuthentication(builder, null, username, password);
|
||||||
return builder.build();
|
return builder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static OkHttpClient addAuthentication(@NonNull OkHttpClient client, @NonNull String host, @NonNull String username, @NonNull String password) {
|
public static OkHttpClient addAuthentication(@NonNull OkHttpClient client, @NonNull String host, @NonNull String username, @NonNull String password) {
|
||||||
return client.newBuilder()
|
OkHttpClient.Builder builder = client.newBuilder();
|
||||||
.authenticator(new BasicDigestAuthenticator(host, username, password))
|
addAuthentication(builder, host, username, password);
|
||||||
.build();
|
return builder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -140,18 +135,4 @@ public class HttpClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
static class PreemptiveAuthenticationInterceptor implements Interceptor {
|
|
||||||
final String username, password;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Response intercept(Chain chain) throws IOException {
|
|
||||||
App.log.fine("Adding basic authorization header for user " + username);
|
|
||||||
Request request = chain.request().newBuilder()
|
|
||||||
.header("Authorization", Credentials.basic(username, password))
|
|
||||||
.build();
|
|
||||||
return chain.proceed(request);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -114,17 +114,6 @@ public class AccountSettingsActivity extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
final SwitchPreferenceCompat prefPreemptive = (SwitchPreferenceCompat)findPreference("preemptive");
|
|
||||||
prefPreemptive.setChecked(settings.preemptiveAuth());
|
|
||||||
prefPreemptive.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
|
||||||
@Override
|
|
||||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
|
||||||
settings.preemptiveAuth((Boolean)newValue);
|
|
||||||
refresh();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// category: synchronization
|
// category: synchronization
|
||||||
final ListPreference prefSyncContacts = (ListPreference)findPreference("sync_interval_contacts");
|
final ListPreference prefSyncContacts = (ListPreference)findPreference("sync_interval_contacts");
|
||||||
final Long syncIntervalContacts = settings.getSyncInterval(ContactsContract.AUTHORITY);
|
final Long syncIntervalContacts = settings.getSyncInterval(ContactsContract.AUTHORITY);
|
||||||
|
@ -213,7 +213,6 @@ public class DebugInfoActivity extends AppCompatActivity implements LoaderManage
|
|||||||
" Address book sync. interval: ").append(syncStatus(settings, ContactsContract.AUTHORITY)).append("\n" +
|
" Address book sync. interval: ").append(syncStatus(settings, ContactsContract.AUTHORITY)).append("\n" +
|
||||||
" Calendar sync. interval: ").append(syncStatus(settings, CalendarContract.AUTHORITY)).append("\n" +
|
" Calendar sync. interval: ").append(syncStatus(settings, CalendarContract.AUTHORITY)).append("\n" +
|
||||||
" OpenTasks sync. interval: ").append(syncStatus(settings, "org.dmfs.tasks")).append("\n" +
|
" OpenTasks sync. interval: ").append(syncStatus(settings, "org.dmfs.tasks")).append("\n" +
|
||||||
" Preemptive auth: ").append(settings.preemptiveAuth()).append("\n" +
|
|
||||||
" WiFi only: ").append(settings.getSyncWifiOnly());
|
" WiFi only: ").append(settings.getSyncWifiOnly());
|
||||||
if (settings.getSyncWifiOnlySSID() != null)
|
if (settings.getSyncWifiOnlySSID() != null)
|
||||||
report.append(", SSID: ").append(settings.getSyncWifiOnlySSID());
|
report.append(", SSID: ").append(settings.getSyncWifiOnlySSID());
|
||||||
|
@ -107,7 +107,7 @@ public class AccountDetailsFragment extends Fragment {
|
|||||||
Account account = new Account(accountName, Constants.ACCOUNT_TYPE);
|
Account account = new Account(accountName, Constants.ACCOUNT_TYPE);
|
||||||
|
|
||||||
// create Android account
|
// create Android account
|
||||||
Bundle userData = AccountSettings.initialUserData(config.userName, config.preemptive);
|
Bundle userData = AccountSettings.initialUserData(config.userName);
|
||||||
App.log.log(Level.INFO, "Creating Android account with initial config", new Object[] { account, userData });
|
App.log.log(Level.INFO, "Creating Android account with initial config", new Object[] { account, userData });
|
||||||
|
|
||||||
AccountManager accountManager = AccountManager.get(getContext());
|
AccountManager accountManager = AccountManager.get(getContext());
|
||||||
|
@ -78,7 +78,7 @@ public class DavResourceFinder {
|
|||||||
log.addHandler(logBuffer);
|
log.addHandler(logBuffer);
|
||||||
|
|
||||||
httpClient = HttpClient.create(log);
|
httpClient = HttpClient.create(log);
|
||||||
httpClient = HttpClient.addAuthentication(httpClient, credentials.userName, credentials.password, credentials.authPreemptive);
|
httpClient = HttpClient.addAuthentication(httpClient, credentials.userName, credentials.password);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -88,7 +88,7 @@ public class DavResourceFinder {
|
|||||||
calDavConfig = findInitialConfiguration(Service.CALDAV);
|
calDavConfig = findInitialConfiguration(Service.CALDAV);
|
||||||
|
|
||||||
return new Configuration(
|
return new Configuration(
|
||||||
credentials.userName, credentials.password, credentials.authPreemptive,
|
credentials.userName, credentials.password,
|
||||||
cardDavConfig, calDavConfig,
|
cardDavConfig, calDavConfig,
|
||||||
logBuffer.toString()
|
logBuffer.toString()
|
||||||
);
|
);
|
||||||
@ -371,7 +371,6 @@ public class DavResourceFinder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public final String userName, password;
|
public final String userName, password;
|
||||||
public final boolean preemptive;
|
|
||||||
|
|
||||||
public final ServiceInfo cardDAV;
|
public final ServiceInfo cardDAV;
|
||||||
public final ServiceInfo calDAV;
|
public final ServiceInfo calDAV;
|
||||||
|
@ -19,7 +19,6 @@ import lombok.RequiredArgsConstructor;
|
|||||||
public class LoginCredentials implements Parcelable {
|
public class LoginCredentials implements Parcelable {
|
||||||
public final URI uri;
|
public final URI uri;
|
||||||
public final String userName, password;
|
public final String userName, password;
|
||||||
public final boolean authPreemptive;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int describeContents() {
|
public int describeContents() {
|
||||||
@ -31,7 +30,6 @@ public class LoginCredentials implements Parcelable {
|
|||||||
dest.writeSerializable(uri);
|
dest.writeSerializable(uri);
|
||||||
dest.writeString(userName);
|
dest.writeString(userName);
|
||||||
dest.writeString(password);
|
dest.writeString(password);
|
||||||
dest.writeValue(authPreemptive);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final Creator CREATOR = new Creator<LoginCredentials>() {
|
public static final Creator CREATOR = new Creator<LoginCredentials>() {
|
||||||
@ -39,8 +37,7 @@ public class LoginCredentials implements Parcelable {
|
|||||||
public LoginCredentials createFromParcel(Parcel source) {
|
public LoginCredentials createFromParcel(Parcel source) {
|
||||||
return new LoginCredentials(
|
return new LoginCredentials(
|
||||||
(URI)source.readSerializable(),
|
(URI)source.readSerializable(),
|
||||||
source.readString(), source.readString(),
|
source.readString(), source.readString()
|
||||||
(boolean)source.readValue(null)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,7 +43,6 @@ public class LoginCredentialsFragment extends Fragment implements CompoundButton
|
|||||||
LinearLayout urlDetails;
|
LinearLayout urlDetails;
|
||||||
EditText editBaseURL, editUserName;
|
EditText editBaseURL, editUserName;
|
||||||
EditPassword editUrlPassword;
|
EditPassword editUrlPassword;
|
||||||
CheckBox checkPreemptiveAuth;
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -60,7 +59,6 @@ public class LoginCredentialsFragment extends Fragment implements CompoundButton
|
|||||||
editBaseURL = (EditText)v.findViewById(R.id.base_url);
|
editBaseURL = (EditText)v.findViewById(R.id.base_url);
|
||||||
editUserName = (EditText)v.findViewById(R.id.user_name);
|
editUserName = (EditText)v.findViewById(R.id.user_name);
|
||||||
editUrlPassword = (EditPassword)v.findViewById(R.id.url_password);
|
editUrlPassword = (EditPassword)v.findViewById(R.id.url_password);
|
||||||
checkPreemptiveAuth = (CheckBox)v.findViewById(R.id.preemptive_auth);
|
|
||||||
|
|
||||||
radioUseEmail.setOnCheckedChangeListener(this);
|
radioUseEmail.setOnCheckedChangeListener(this);
|
||||||
radioUseURL.setOnCheckedChangeListener(this);
|
radioUseURL.setOnCheckedChangeListener(this);
|
||||||
@ -114,7 +112,7 @@ public class LoginCredentialsFragment extends Fragment implements CompoundButton
|
|||||||
valid = false;
|
valid = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return valid ? new LoginCredentials(uri, email, password, true) : null;
|
return valid ? new LoginCredentials(uri, email, password) : null;
|
||||||
|
|
||||||
} else if (radioUseURL.isChecked()) {
|
} else if (radioUseURL.isChecked()) {
|
||||||
URI uri = null;
|
URI uri = null;
|
||||||
@ -159,7 +157,7 @@ public class LoginCredentialsFragment extends Fragment implements CompoundButton
|
|||||||
valid = false;
|
valid = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return valid ? new LoginCredentials(uri, userName, password, checkPreemptiveAuth.isChecked()) : null;
|
return valid ? new LoginCredentials(uri, userName, password) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
@ -94,13 +94,6 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:hint="@string/login_password"/>
|
android:hint="@string/login_password"/>
|
||||||
|
|
||||||
<CheckBox
|
|
||||||
android:id="@+id/preemptive_auth"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/login_auth_preemptive"
|
|
||||||
android:checked="true"/>
|
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
</RadioGroup>
|
</RadioGroup>
|
||||||
|
@ -25,13 +25,6 @@
|
|||||||
android:summary="@string/settings_password_summary"
|
android:summary="@string/settings_password_summary"
|
||||||
android:dialogTitle="@string/settings_enter_password" />
|
android:dialogTitle="@string/settings_enter_password" />
|
||||||
|
|
||||||
<SwitchPreferenceCompat
|
|
||||||
android:key="preemptive"
|
|
||||||
android:persistent="false"
|
|
||||||
android:title="@string/settings_preemptive"
|
|
||||||
android:summaryOn="@string/settings_preemptive_on"
|
|
||||||
android:summaryOff="@string/settings_preemptive_off" />
|
|
||||||
|
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
|
|
||||||
<PreferenceCategory android:title="@string/settings_sync">
|
<PreferenceCategory android:title="@string/settings_sync">
|
||||||
|
@ -1 +1 @@
|
|||||||
Subproject commit bf2004e9052241345c435c2d46090ab9baf43eb8
|
Subproject commit ff40bfd61f40a445b0e9414ae15b4a02fb95f64b
|
Loading…
Reference in New Issue
Block a user