Merge branch 'master' into 'master'

Add HTTP Basic Authentication to ApkDownloader

In order to download protected files, the APKDownloader must be able to supply HTTP Basic Authentication headers to the server.

See merge request !178
This commit is contained in:
Peter Serwylo 2015-12-22 08:40:08 +00:00
commit 07eb633f0f
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.util.Iterator;
import java.util.List;
import org.fdroid.fdroid.data.Credentials;
interface AppDetailsData {
App getApp();
@ -855,8 +856,20 @@ public class AppDetails extends AppCompatActivity implements ProgressListener, A
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) {
downloadHandler = new ApkDownloader(getBaseContext(), app, apk, repoAddress);
downloadHandler.setCredentials(getRepoCredentials(apk));
localBroadcastManager.registerReceiver(downloaderProgressReceiver,
new IntentFilter(Downloader.LOCAL_ACTION_PROGRESS));
downloadHandler.setProgressListener(this);

View File

@ -96,8 +96,7 @@ public class RepoUpdater {
try {
downloader = DownloaderFactory.create(context,
getIndexAddress(), File.createTempFile("index-", "-downloaded", context.getCacheDir()),
repo.username,
repo.password
repo.getCredentials()
);
downloader.setCacheTag(repo.lastetag);
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
*/
void authenticate(final HttpURLConnection connection);
}

View File

@ -9,6 +9,7 @@ import org.fdroid.fdroid.Utils;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Date;
import org.fdroid.fdroid.net.auth.HttpBasicCredentials;
public class Repo extends ValueObject {
@ -148,6 +149,21 @@ public class Repo extends ValueObject {
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) {
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.IOException;
import java.security.NoSuchAlgorithmException;
import org.fdroid.fdroid.data.Credentials;
/**
* Downloads and verifies (against the Apk.hash) the apk file.
@ -77,6 +78,7 @@ public class ApkDownloader implements AsyncDownloader.Listener {
private ProgressListener listener;
private AsyncDownloader dlWrapper;
private Credentials credentials;
private boolean isComplete;
private final long id = ++downloadIdCounter;
@ -195,7 +197,7 @@ public class ApkDownloader implements AsyncDownloader.Listener {
Utils.debugLog(TAG, "Downloading apk from " + remoteAddress + " to " + localFile);
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();
return true;
} catch (IOException e) {
@ -292,4 +294,8 @@ public class ApkDownloader implements AsyncDownloader.Listener {
public int getTotalBytes() {
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.IOException;
import java.net.URL;
import org.fdroid.fdroid.data.Credentials;
public class DownloaderFactory {
@ -45,10 +46,10 @@ public class DownloaderFactory {
public static Downloader create(Context context, URL url, File destFile)
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 {
if (isBluetoothAddress(url)) {
String macAddress = url.getHost().replace("-", ":");
@ -60,7 +61,7 @@ public class DownloaderFactory {
if (isLocalFile(url)) {
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) {
@ -71,11 +72,11 @@ public class DownloaderFactory {
return "file".equalsIgnoreCase(url.getProtocol());
}
public static AsyncDownloader createAsync(Context context, String urlString, File destFile, String title, String id, AsyncDownloader.Listener listener) throws IOException {
return createAsync(context, new URL(urlString), destFile, title, id, listener);
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, 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 {
// To re-enable, fix the following:
// * https://gitlab.com/fdroid/fdroidclient/issues/445
@ -85,7 +86,7 @@ public class DownloaderFactory {
return new AsyncDownloaderFromAndroid(context, listener, title, id, url.toString(), destFile);
}
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) {

View File

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

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