Added HTTP Basic Authentication to ApkDownloader, added Credentials interface

and HttpBasicCredentials implementation to prepare HttpDownloader for other
authentication methods.
This commit is contained in:
Christian Morgner 2015-12-07 16:07:03 +01:00
parent 2067f15b27
commit a29bf3d9c8
8 changed files with 138 additions and 21 deletions

View File

@ -97,6 +97,7 @@ import org.fdroid.fdroid.net.Downloader;
import java.io.File; import java.io.File;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import org.fdroid.fdroid.data.Credentials;
interface AppDetailsData { interface AppDetailsData {
App getApp(); App getApp();
@ -855,8 +856,20 @@ public class AppDetails extends AppCompatActivity implements ProgressListener, A
return repo.address; return repo.address;
} }
@Nullable
private Credentials getRepoCredentials(Apk apk) {
final String[] projection = {RepoProvider.DataColumns.USERNAME, RepoProvider.DataColumns.PASSWORD};
Repo repo = RepoProvider.Helper.findById(this, apk.repo, projection);
if (repo == null || repo.username == null || repo.password == null) {
return null;
}
return repo.getCredentials();
}
private void startDownload(Apk apk, String repoAddress) { private void startDownload(Apk apk, String repoAddress) {
downloadHandler = new ApkDownloader(getBaseContext(), app, apk, repoAddress); downloadHandler = new ApkDownloader(getBaseContext(), app, apk, repoAddress);
downloadHandler.setCredentials(getRepoCredentials(apk));
localBroadcastManager.registerReceiver(downloaderProgressReceiver, localBroadcastManager.registerReceiver(downloaderProgressReceiver,
new IntentFilter(Downloader.LOCAL_ACTION_PROGRESS)); new IntentFilter(Downloader.LOCAL_ACTION_PROGRESS));
downloadHandler.setProgressListener(this); downloadHandler.setProgressListener(this);

View File

@ -96,8 +96,7 @@ public class RepoUpdater {
try { try {
downloader = DownloaderFactory.create(context, downloader = DownloaderFactory.create(context,
getIndexAddress(), File.createTempFile("index-", "-downloaded", context.getCacheDir()), getIndexAddress(), File.createTempFile("index-", "-downloaded", context.getCacheDir()),
repo.username, repo.getCredentials()
repo.password
); );
downloader.setCacheTag(repo.lastetag); downloader.setCacheTag(repo.lastetag);
downloader.downloadUninterrupted(); downloader.downloadUninterrupted();

View File

@ -0,0 +1,36 @@
/*
* Copyright (C) 2015 Christian Morgner (christian.morgner@structr.com)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 3
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*/
package org.fdroid.fdroid.data;
import java.net.HttpURLConnection;
/**
* Credentials to authenticate HTTP requests. Implementations if this interface
* should encapsulate the authentication of an HTTP request in the authenticate
* method.
*/
public interface Credentials {
/**
* Implement this method to provide authentication for the given connection.
* @param connection the HTTP connection to authenticate
*/
public void authenticate(final HttpURLConnection connection);
}

View File

@ -9,6 +9,7 @@ import org.fdroid.fdroid.Utils;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
import java.util.Date; import java.util.Date;
import org.fdroid.fdroid.net.auth.HttpBasicCredentials;
public class Repo extends ValueObject { public class Repo extends ValueObject {
@ -148,6 +149,21 @@ public class Repo extends ValueObject {
return value; return value;
} }
/**
* Returns the credentials for this repo, or null of no authentication
* method is configured.
* @return the credentials or null
*/
public Credentials getCredentials() {
// return the only credentials implementation we have right now
if (!TextUtils.isEmpty(username) && !TextUtils.isEmpty(password)) {
return new HttpBasicCredentials(username, password);
}
return null;
}
public void setValues(ContentValues values) { public void setValues(ContentValues values) {
if (values.containsKey(RepoProvider.DataColumns._ID)) { if (values.containsKey(RepoProvider.DataColumns._ID)) {

View File

@ -39,6 +39,7 @@ import org.fdroid.fdroid.data.SanitizedFile;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import org.fdroid.fdroid.data.Credentials;
/** /**
* Downloads and verifies (against the Apk.hash) the apk file. * Downloads and verifies (against the Apk.hash) the apk file.
@ -77,6 +78,7 @@ public class ApkDownloader implements AsyncDownloader.Listener {
private ProgressListener listener; private ProgressListener listener;
private AsyncDownloader dlWrapper; private AsyncDownloader dlWrapper;
private Credentials credentials;
private boolean isComplete; private boolean isComplete;
private final long id = ++downloadIdCounter; private final long id = ++downloadIdCounter;
@ -195,7 +197,7 @@ public class ApkDownloader implements AsyncDownloader.Listener {
Utils.debugLog(TAG, "Downloading apk from " + remoteAddress + " to " + localFile); Utils.debugLog(TAG, "Downloading apk from " + remoteAddress + " to " + localFile);
try { try {
dlWrapper = DownloaderFactory.createAsync(context, remoteAddress, localFile, app.name + " " + curApk.version, curApk.id, this); dlWrapper = DownloaderFactory.createAsync(context, remoteAddress, localFile, app.name + " " + curApk.version, curApk.id, credentials, this);
dlWrapper.download(); dlWrapper.download();
return true; return true;
} catch (IOException e) { } catch (IOException e) {
@ -292,4 +294,8 @@ public class ApkDownloader implements AsyncDownloader.Listener {
public int getTotalBytes() { public int getTotalBytes() {
return dlWrapper != null ? dlWrapper.getTotalBytes() : 0; return dlWrapper != null ? dlWrapper.getTotalBytes() : 0;
} }
public void setCredentials(final Credentials credentials) {
this.credentials = credentials;
}
} }

View File

@ -11,6 +11,7 @@ import org.fdroid.fdroid.Utils;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.net.URL; import java.net.URL;
import org.fdroid.fdroid.data.Credentials;
public class DownloaderFactory { public class DownloaderFactory {
@ -45,10 +46,10 @@ public class DownloaderFactory {
public static Downloader create(Context context, URL url, File destFile) public static Downloader create(Context context, URL url, File destFile)
throws IOException { throws IOException {
return create(context, url, destFile, null, null); return create(context, url, destFile, null);
} }
public static Downloader create(Context context, URL url, File destFile, final String username, final String password) public static Downloader create(Context context, URL url, File destFile, Credentials credentials)
throws IOException { throws IOException {
if (isBluetoothAddress(url)) { if (isBluetoothAddress(url)) {
String macAddress = url.getHost().replace("-", ":"); String macAddress = url.getHost().replace("-", ":");
@ -60,7 +61,7 @@ public class DownloaderFactory {
if (isLocalFile(url)) { if (isLocalFile(url)) {
return new LocalFileDownloader(context, url, destFile); return new LocalFileDownloader(context, url, destFile);
} }
return new HttpDownloader(context, url, destFile, username, password); return new HttpDownloader(context, url, destFile, credentials);
} }
private static boolean isBluetoothAddress(URL url) { private static boolean isBluetoothAddress(URL url) {
@ -71,11 +72,11 @@ public class DownloaderFactory {
return "file".equalsIgnoreCase(url.getProtocol()); return "file".equalsIgnoreCase(url.getProtocol());
} }
public static AsyncDownloader createAsync(Context context, String urlString, File destFile, String title, String id, AsyncDownloader.Listener listener) throws IOException { public static AsyncDownloader createAsync(Context context, String urlString, File destFile, String title, String id, Credentials credentials, AsyncDownloader.Listener listener) throws IOException {
return createAsync(context, new URL(urlString), destFile, title, id, listener); return createAsync(context, new URL(urlString), destFile, title, id, credentials, listener);
} }
public static AsyncDownloader createAsync(Context context, URL url, File destFile, String title, String id, AsyncDownloader.Listener listener) public static AsyncDownloader createAsync(Context context, URL url, File destFile, String title, String id, Credentials credentials, AsyncDownloader.Listener listener)
throws IOException { throws IOException {
// To re-enable, fix the following: // To re-enable, fix the following:
// * https://gitlab.com/fdroid/fdroidclient/issues/445 // * https://gitlab.com/fdroid/fdroidclient/issues/445
@ -85,7 +86,7 @@ public class DownloaderFactory {
return new AsyncDownloaderFromAndroid(context, listener, title, id, url.toString(), destFile); return new AsyncDownloaderFromAndroid(context, listener, title, id, url.toString(), destFile);
} }
Utils.debugLog(TAG, "Using AsyncDownloadWrapper"); Utils.debugLog(TAG, "Using AsyncDownloadWrapper");
return new AsyncDownloadWrapper(create(context, url, destFile), listener); return new AsyncDownloadWrapper(create(context, url, destFile, credentials), listener);
} }
static boolean isOnionAddress(URL url) { static boolean isOnionAddress(URL url) {

View File

@ -1,10 +1,9 @@
package org.fdroid.fdroid.net; package org.fdroid.fdroid.net;
import android.content.Context; import android.content.Context;
import android.text.TextUtils;
import android.util.Log; import android.util.Log;
import com.nostra13.universalimageloader.core.download.BaseImageDownloader;
import org.apache.commons.net.util.Base64;
import org.fdroid.fdroid.FDroidApp; import org.fdroid.fdroid.FDroidApp;
import org.fdroid.fdroid.Preferences; import org.fdroid.fdroid.Preferences;
import org.fdroid.fdroid.Utils; import org.fdroid.fdroid.Utils;
@ -22,6 +21,7 @@ import java.net.SocketAddress;
import java.net.URL; import java.net.URL;
import javax.net.ssl.SSLHandshakeException; import javax.net.ssl.SSLHandshakeException;
import org.fdroid.fdroid.data.Credentials;
public class HttpDownloader extends Downloader { public class HttpDownloader extends Downloader {
private static final String TAG = "HttpDownloader"; private static final String TAG = "HttpDownloader";
@ -30,21 +30,19 @@ public class HttpDownloader extends Downloader {
protected static final String HEADER_FIELD_ETAG = "ETag"; protected static final String HEADER_FIELD_ETAG = "ETag";
protected HttpURLConnection connection; protected HttpURLConnection connection;
private final String username; private Credentials credentials;
private final String password;
private int statusCode = -1; private int statusCode = -1;
HttpDownloader(Context context, URL url, File destFile) HttpDownloader(Context context, URL url, File destFile)
throws FileNotFoundException, MalformedURLException { throws FileNotFoundException, MalformedURLException {
this(context, url, destFile, null, null); this(context, url, destFile, null);
} }
HttpDownloader(Context context, URL url, File destFile, final String username, final String password) HttpDownloader(Context context, URL url, File destFile, final Credentials credentials)
throws FileNotFoundException, MalformedURLException { throws FileNotFoundException, MalformedURLException {
super(context, url, destFile); super(context, url, destFile);
this.username = username; this.credentials = credentials;
this.password = password;
} }
/** /**
@ -100,9 +98,8 @@ public class HttpDownloader extends Downloader {
} else { } else {
connection = (HttpURLConnection) sourceUrl.openConnection(); connection = (HttpURLConnection) sourceUrl.openConnection();
if (!TextUtils.isEmpty(username) && !TextUtils.isEmpty(password)) { if (credentials != null) {
// add authorization header from username / password if set credentials.authenticate((HttpURLConnection)connection);
connection.setRequestProperty("Authorization", "Basic " + Base64.encodeBase64String((username + ":" + password).getBytes()));
} }
} }
} }

View File

@ -0,0 +1,49 @@
/*
* Copyright (C) 2015 Christian Morgner (christian.morgner@structr.com)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 3
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*/
package org.fdroid.fdroid.net.auth;
import android.text.TextUtils;
import java.net.HttpURLConnection;
import org.apache.commons.net.util.Base64;
import org.fdroid.fdroid.data.Credentials;
/**
* Credentials implementation for HTTP Basic Authentication.
*/
public class HttpBasicCredentials implements Credentials {
private final String username;
private final String password;
public HttpBasicCredentials(final String username, final String password) {
this.username = username;
this.password = password;
}
@Override
public void authenticate(final HttpURLConnection connection) {
if (!TextUtils.isEmpty(username) && !TextUtils.isEmpty(password)) {
// add authorization header from username / password if set
connection.setRequestProperty("Authorization", "Basic " + Base64.encodeBase64String((username + ":" + password).getBytes()));
}
}
}