1
0
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:
Ricki Hirner 2016-08-05 14:55:44 +02:00
parent ae145d897e
commit bab84d7d0f
12 changed files with 30 additions and 83 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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