diff --git a/library/build.gradle b/library/build.gradle index 9537b843d93d556f9286ed18d359f259b64f83a7..5752a2f0edb96b88f46576ef6db7b296c970fdc5 100644 --- a/library/build.gradle +++ b/library/build.gradle @@ -47,7 +47,7 @@ configurations { def versionMajor = 1 def versionMinor = 0 -def versionPatch = 6 +def versionPatch = 7 def upstreamVersion = "2.17" def releasePatch = "release" def libName = "Nextcloud-Android-Library" @@ -64,6 +64,8 @@ dependencies { implementation "androidx.core:core-ktx:1.10.1" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" + implementation 'net.openid:appauth:0.11.1' + spotbugsPlugins 'com.h3xstream.findsecbugs:findsecbugs-plugin:1.12.0' spotbugsPlugins 'com.mebigfatguy.fb-contrib:fb-contrib:7.6.2' diff --git a/library/src/main/java/com/nextcloud/common/NextcloudClient.kt b/library/src/main/java/com/nextcloud/common/NextcloudClient.kt index 9473604f5f39ddf5034e661be5df376902978b2a..6029fabd0ab45ab7dd113096931f95b92be5fc27 100644 --- a/library/src/main/java/com/nextcloud/common/NextcloudClient.kt +++ b/library/src/main/java/com/nextcloud/common/NextcloudClient.kt @@ -55,6 +55,7 @@ class NextcloudClient private constructor( val client: OkHttpClient ) : NextcloudUriProvider by delegate { var followRedirects = true + var oidcLoginWithToken = false constructor( baseUri: Uri, @@ -73,7 +74,7 @@ class NextcloudClient private constructor( @JvmStatic val TAG = NextcloudClient::class.java.simpleName - private fun createDefaultClient(context: Context): OkHttpClient { + private fun createDefaultClient(context: Context, cookieJar: CookieJar = CookieJar.NO_COOKIES): OkHttpClient { val trustManager = AdvancedX509TrustManager(NetworkUtils.getKnownServersStore(context)) val sslContext = NetworkUtils.getSSLContext() @@ -82,7 +83,7 @@ class NextcloudClient private constructor( val sslSocketFactory = sslContext.socketFactory return OkHttpClient.Builder() - .cookieJar(CookieJar.NO_COOKIES) + .cookieJar(cookieJar) .connectTimeout(DEFAULT_CONNECTION_TIMEOUT_LONG, TimeUnit.MILLISECONDS) .readTimeout(DEFAULT_DATA_TIMEOUT_LONG, TimeUnit.MILLISECONDS) .callTimeout(DEFAULT_CONNECTION_TIMEOUT_LONG + DEFAULT_DATA_TIMEOUT_LONG, TimeUnit.MILLISECONDS) @@ -97,8 +98,9 @@ class NextcloudClient private constructor( baseUri: Uri, userId: String, credentials: String, - context: Context - ) : this(baseUri, userId, credentials, createDefaultClient(context)) + context: Context, + cookieJar: CookieJar = CookieJar.NO_COOKIES + ) : this(baseUri, userId, credentials, createDefaultClient(context, cookieJar)) @Suppress("TooGenericExceptionCaught") fun execute(remoteOperation: RemoteOperation): RemoteOperationResult { diff --git a/library/src/main/java/com/nextcloud/common/OkHttpMethodBase.kt b/library/src/main/java/com/nextcloud/common/OkHttpMethodBase.kt index e3c80f155d012fd3fbe7d3d399389750335ac40f..adb52013524e9240bf01559a55e94fbd3f037fa9 100644 --- a/library/src/main/java/com/nextcloud/common/OkHttpMethodBase.kt +++ b/library/src/main/java/com/nextcloud/common/OkHttpMethodBase.kt @@ -147,6 +147,11 @@ abstract class OkHttpMethodBase( temp.header(RemoteOperation.OCS_API_HEADER, RemoteOperation.OCS_API_HEADER_VALUE) } + if (nextcloudClient.oidcLoginWithToken) { + temp.header(RemoteOperation.OIDC_LOGIN_WITH_TOKEN, "true") + temp.header(RemoteOperation.OCS_API_HEADER, "true") + } + applyType(temp) val request = temp.build() diff --git a/library/src/main/java/com/nextcloud/common/OkHttpPersistentCookieJar.kt b/library/src/main/java/com/nextcloud/common/OkHttpPersistentCookieJar.kt new file mode 100644 index 0000000000000000000000000000000000000000..dfb25ce4a89fbed4c8bfc383aaca9b4a9e95938e --- /dev/null +++ b/library/src/main/java/com/nextcloud/common/OkHttpPersistentCookieJar.kt @@ -0,0 +1,78 @@ +/* + * Copyright MURENA SAS 2024 + * 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, see . + */ + +package com.nextcloud.common + +import android.accounts.Account +import android.accounts.AccountManager +import android.content.Context +import com.owncloud.android.lib.common.accounts.AccountUtils.Constants +import okhttp3.Cookie +import okhttp3.CookieJar +import okhttp3.HttpUrl + +class OkHttpPersistentCookieJar(context: Context, private val account: Account) : CookieJar { + + private val accountManager = AccountManager.get(context) + + override fun loadForRequest(url: HttpUrl): List { + return getCookieMap(url).values.filter { + it.matches(url) + } + } + + override fun saveFromResponse(url: HttpUrl, cookies: List) { + val cookieList = cookies.filter { + it.expiresAt > System.currentTimeMillis() + } + + if (cookieList.isEmpty()) { + return + } + + val cookieMap = getCookieMap(url) + + // replace old cookie with new one + cookieList.forEach { + cookieMap[it.name] = it + } + + val cookieString = + cookieMap.values.joinToString(separator = Constants.OKHTTP_COOKIE_SEPARATOR) + + accountManager.setUserData(account, Constants.KEY_OKHTTP_COOKIES, cookieString) + } + + private fun getCookieMap(url: HttpUrl): HashMap { + val result = HashMap() + val cookiesString = + accountManager.getUserData(account, Constants.KEY_OKHTTP_COOKIES) ?: return HashMap() + + val cookies = cookiesString.split(Constants.OKHTTP_COOKIE_SEPARATOR.toRegex()) + .dropLastWhile { it.isEmpty() } + .toTypedArray() + + cookies.forEach { + val cookie = Cookie.parse(url, it) ?: return@forEach + + if (cookie.expiresAt > System.currentTimeMillis()) { + result[cookie.name] = cookie + } + } + + return result + } +} diff --git a/library/src/main/java/com/owncloud/android/lib/common/BearerCredentials.java b/library/src/main/java/com/owncloud/android/lib/common/BearerCredentials.java new file mode 100644 index 0000000000000000000000000000000000000000..d1e1a071e680136e29c65dc089f1c8e0033fa797 --- /dev/null +++ b/library/src/main/java/com/owncloud/android/lib/common/BearerCredentials.java @@ -0,0 +1,52 @@ +/* + * Copyright MURENA SAS 2024 + * 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, see . + */ + +package com.owncloud.android.lib.common; + +import androidx.annotation.NonNull; + +import org.apache.commons.httpclient.Credentials; + +import java.util.Objects; + +public class BearerCredentials implements Credentials { + + private final String accessToken; + + public BearerCredentials(String accessToken) { + super(); + this.accessToken = accessToken; + } + + @NonNull + @Override + public String toString() { + return accessToken; + } + + @Override + public boolean equals(Object object) { + if (this == object) return true; + if (object == null || getClass() != object.getClass()) return false; + BearerCredentials that = (BearerCredentials) object; + return Objects.equals(accessToken, that.accessToken); + } + + @Override + public int hashCode() { + return Objects.hash(accessToken); + } +} diff --git a/library/src/main/java/com/owncloud/android/lib/common/OwnCloudBearerCredentials.java b/library/src/main/java/com/owncloud/android/lib/common/OwnCloudBearerCredentials.java new file mode 100644 index 0000000000000000000000000000000000000000..0a55b650fdc0d90826eec94424f22827a8130c39 --- /dev/null +++ b/library/src/main/java/com/owncloud/android/lib/common/OwnCloudBearerCredentials.java @@ -0,0 +1,111 @@ +/* + * Copyright MURENA SAS 2024 + * 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, see . + */ + +package com.owncloud.android.lib.common; + +import android.os.Parcel; + +import androidx.annotation.NonNull; + +import org.apache.commons.httpclient.auth.AuthPolicy; +import org.apache.commons.httpclient.auth.AuthScope; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +public class OwnCloudBearerCredentials implements OwnCloudCredentials { + + private final String userName; + private final String accessToken; + + public OwnCloudBearerCredentials(String userName, String accessToken) { + this.accessToken = accessToken != null ? accessToken : ""; + this.userName = userName != null ? userName : ""; + } + + @Override + public void applyTo(OwnCloudClient client) { + List authPrefs = new ArrayList<>(1); + authPrefs.add("Bearer"); + + client.getParams().setParameter(AuthPolicy.AUTH_SCHEME_PRIORITY, authPrefs); + client.getParams().setAuthenticationPreemptive(false); + client.getParams().setCredentialCharset(OwnCloudCredentialsFactory.CREDENTIAL_CHARSET); + client.getState().setCredentials(AuthScope.ANY, new BearerCredentials(accessToken)); + } + + @Override + public String getUsername() { + return userName; + } + + @Override + public String getAuthToken() { + return accessToken; + } + + @Override + public boolean authTokenExpires() { + return false; + } + + @Override + public String toOkHttpCredentials() { + return "Bearer " + accessToken; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeString(userName); + dest.writeString(accessToken); + } + + protected OwnCloudBearerCredentials(Parcel in) { + userName = in.readString(); + accessToken = in.readString(); + } + + public static final Creator CREATOR = new Creator<>() { + @Override + public OwnCloudBearerCredentials createFromParcel(Parcel source) { + return new OwnCloudBearerCredentials(source); + } + + @Override + public OwnCloudBearerCredentials[] newArray(int size) { + return new OwnCloudBearerCredentials[size]; + } + }; + + @Override + public boolean equals(Object object) { + if (this == object) return true; + if (object == null || getClass() != object.getClass()) return false; + OwnCloudBearerCredentials that = (OwnCloudBearerCredentials) object; + return Objects.equals(userName, that.userName) && Objects.equals(accessToken, that.accessToken); + } + + @Override + public int hashCode() { + return Objects.hash(userName, accessToken); + } +} diff --git a/library/src/main/java/com/owncloud/android/lib/common/OwnCloudClient.java b/library/src/main/java/com/owncloud/android/lib/common/OwnCloudClient.java index 9ff9891bc00bc541020a72dd11e5ab1258e9488b..9fea5ed96b9423a4f5d79355af1975a8214bf5d5 100644 --- a/library/src/main/java/com/owncloud/android/lib/common/OwnCloudClient.java +++ b/library/src/main/java/com/owncloud/android/lib/common/OwnCloudClient.java @@ -31,6 +31,7 @@ import com.nextcloud.common.DNSCache; import com.nextcloud.common.NextcloudUriDelegate; import com.owncloud.android.lib.common.accounts.AccountUtils; import com.owncloud.android.lib.common.network.RedirectionPath; +import com.owncloud.android.lib.common.operations.RemoteOperation; import com.owncloud.android.lib.common.utils.Log_OC; import org.apache.commons.httpclient.Cookie; @@ -182,6 +183,8 @@ public class OwnCloudClient extends HttpClient { */ @Override public int executeMethod(HttpMethod method) throws IOException { + addBearerCredentialIfRequired(method); + final String hostname = method.getURI().getHost(); try { @@ -232,6 +235,14 @@ public class OwnCloudClient extends HttpClient { } } + private void addBearerCredentialIfRequired(HttpMethod method) { + if (credentials instanceof OwnCloudBearerCredentials) { + method.setRequestHeader("Authorization", "Bearer " + credentials.getAuthToken()); + method.setRequestHeader(RemoteOperation.OIDC_LOGIN_WITH_TOKEN, "true"); + method.setRequestHeader(RemoteOperation.OCS_API_HEADER, "true"); + } + } + /* * if 401 received & cookie is passed on request, there is a chance the invalidated session-cookie is passed. * Retry without passing the cookie. diff --git a/library/src/main/java/com/owncloud/android/lib/common/OwnCloudClientFactory.java b/library/src/main/java/com/owncloud/android/lib/common/OwnCloudClientFactory.java index 760b4f047da0673530a671329da130cd47d6708e..bb4cecdb0a87ad0e34ff1a843d0f6d20539e53e4 100644 --- a/library/src/main/java/com/owncloud/android/lib/common/OwnCloudClientFactory.java +++ b/library/src/main/java/com/owncloud/android/lib/common/OwnCloudClientFactory.java @@ -26,18 +26,16 @@ package com.owncloud.android.lib.common; import android.accounts.Account; import android.accounts.AccountManager; -import android.accounts.AccountManagerFuture; import android.accounts.AuthenticatorException; import android.accounts.OperationCanceledException; import android.app.Activity; import android.content.Context; import android.net.Uri; -import android.os.Bundle; import com.nextcloud.common.NextcloudClient; import com.nextcloud.common.OkHttpCredentialsUtil; +import com.nextcloud.common.OkHttpPersistentCookieJar; import com.nextcloud.common.User; -import com.owncloud.android.lib.common.accounts.AccountTypeUtils; import com.owncloud.android.lib.common.accounts.AccountUtils; import com.owncloud.android.lib.common.accounts.AccountUtils.AccountNotFoundException; import com.owncloud.android.lib.common.network.NetworkUtils; @@ -46,6 +44,8 @@ import com.owncloud.android.lib.common.utils.Log_OC; import java.io.IOException; import java.security.GeneralSecurityException; +import okhttp3.CookieJar; + public class OwnCloudClientFactory { final private static String TAG = OwnCloudClientFactory.class.getSimpleName(); @@ -87,10 +87,7 @@ public class OwnCloudClientFactory { OwnCloudClient client = createOwnCloudClient(baseUri, appContext, true); client.setUserId(userId); - String username = AccountUtils.getUsernameForAccount(account); - String password = AccountUtils.getPassword(am, account); - OwnCloudCredentials credentials = OwnCloudCredentialsFactory.newBasicCredentials(username, password); - + OwnCloudCredentials credentials = AccountUtils.getCredentialsForAccount(appContext, account); client.setCredentials(credentials); // Restore cookies @@ -111,22 +108,7 @@ public class OwnCloudClientFactory { OwnCloudClient client = createOwnCloudClient(baseUri, appContext, true); client.setUserId(userId); - String username = AccountUtils.getUsernameForAccount(account); - //String password = am.getPassword(account); - //String password = am.blockingGetAuthToken(account, MainApp.getAuthTokenTypePass(), - // false); - AccountManagerFuture future = am.getAuthToken(account, - AccountTypeUtils.getAuthTokenTypePass(account.type), null, - currentActivity, null, null); - - Bundle result = future.getResult(); - String password = result.getString(AccountManager.KEY_AUTHTOKEN); - - if (password == null) { - password = am.getPassword(account); - } - - OwnCloudCredentials credentials = OwnCloudCredentialsFactory.newBasicCredentials(username, password); + OwnCloudCredentials credentials = AccountUtils.getCredentialForAccount(appContext, account, currentActivity); client.setCredentials(credentials); @@ -174,7 +156,9 @@ public class OwnCloudClientFactory { String userId, String credentials, Context context, - boolean followRedirects) { + boolean followRedirects, + boolean loginWithToken, + CookieJar cookieJar) { try { NetworkUtils.registerAdvancedSslContext(true, context); } catch (GeneralSecurityException e) { @@ -186,8 +170,9 @@ public class OwnCloudClientFactory { " in the system will be used for HTTPS connections", e); } - NextcloudClient client = new NextcloudClient(uri, userId, credentials, context); + NextcloudClient client = new NextcloudClient(uri, userId, credentials, context, cookieJar); client.setFollowRedirects(followRedirects); + client.setOidcLoginWithToken(loginWithToken); return client; } @@ -214,33 +199,50 @@ public class OwnCloudClientFactory { // TODO avoid calling to getUserData here String userId = AccountUtils.getUpdatedUserId(am, account); String username = AccountUtils.getUsernameForAccount(account); + + if (username == null || username.isEmpty()) { + throw new AccountNotFoundException( + account, + "Username could not be retrieved", + null); + } + + final CookieJar cookieJar = new OkHttpPersistentCookieJar(appContext, account); + + final String accessToken = AccountUtils.getAccessToken(am, account); + if (accessToken != null) { + String credential = "Bearer " + accessToken; + return createNextcloudClient(baseUri, + userId, + credential, + appContext, + true, + true, + cookieJar); + } + String password; try { password = AccountUtils.getPassword(am, account); - if (password == null) { - Log_OC.e(TAG, "Error receiving password token (password==null)"); - throw new AccountNotFoundException(account, "Error receiving password token (password==null)", null); + if (password == null || password.isEmpty()) { + Log_OC.e(TAG, "Error receiving password token (password==null || empty)"); + throw new AccountNotFoundException(account, "Error receiving password token (password==null || empty)", null); } } catch (Exception e) { Log_OC.e(TAG, "Error receiving password token", e); throw new AccountNotFoundException(account, "Error receiving password token", e); } - if (username == null || username.isEmpty() || password.isEmpty()) { - throw new AccountNotFoundException( - account, - "Username or password could not be retrieved", - null); - } - // Restore cookies // TODO v2 cookie handling // AccountUtils.restoreCookies(account, client, appContext); return createNextcloudClient(baseUri, - userId, - OkHttpCredentialsUtil.basic(username, password), - appContext, - true); + userId, + OkHttpCredentialsUtil.basic(username, password), + appContext, + true, + false, + cookieJar); } } diff --git a/library/src/main/java/com/owncloud/android/lib/common/OwnCloudClientManager.java b/library/src/main/java/com/owncloud/android/lib/common/OwnCloudClientManager.java index 99a5c79c6884a3765d3f9b776f5ad7fb6a97b2f9..3344e14e591edf498334f5bdae1823c8b582f02d 100644 --- a/library/src/main/java/com/owncloud/android/lib/common/OwnCloudClientManager.java +++ b/library/src/main/java/com/owncloud/android/lib/common/OwnCloudClientManager.java @@ -32,6 +32,7 @@ import android.content.Context; import android.net.Uri; import android.util.Log; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.nextcloud.common.NextcloudClient; @@ -47,7 +48,6 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; /** - * * @author David A. Velasco * @author masensio * @author Tobias Kaminsky @@ -198,13 +198,6 @@ public class OwnCloudClientManager { if (client == null) { - - // TODO v2 - //client.getParams().setCookiePolicy(CookiePolicy.BROWSER_COMPATIBILITY); - // enable cookie tracking - - //AccountUtils.restoreCookies(accountName, client, context); - account.loadCredentials(context); String credentials = account.getCredentials().toOkHttpCredentials(); @@ -224,7 +217,9 @@ public class OwnCloudClientManager { userId, credentials, context.getApplicationContext(), - true); + true, + isOIDCLogin(account), + AccountUtils.getOkhttpCookieJar(context, accountName)); if (accountName != null) { clientsNewWithKnownUsername.put(accountName, client); @@ -239,12 +234,13 @@ public class OwnCloudClientManager { } } } else { + if (!reusingKnown && Log.isLoggable(TAG, Log.VERBOSE)) { Log_OC.v(TAG, "reusing client for session " + sessionName); } - // TODO v2 - // keepCredentialsUpdated(account, client); - // keepUriUpdated(account, client); + + keepCredentialsUpdated(context, account, client); + keepUriUpdated(account, client); } if (Log.isLoggable(TAG, Log.DEBUG)) { @@ -254,6 +250,23 @@ public class OwnCloudClientManager { return client; } + private void keepCredentialsUpdated(@NonNull Context context, @NonNull OwnCloudAccount account, @NonNull NextcloudClient client) throws OperationCanceledException, AuthenticatorException, IOException { + account.loadCredentials(context); + client.setCredentials(account.getCredentials().toOkHttpCredentials()); + client.setOidcLoginWithToken(isOIDCLogin(account)); + } + + private void keepUriUpdated(@NonNull OwnCloudAccount account, @NonNull NextcloudClient client) { + final Uri recentUri = account.getBaseUri(); + if (!recentUri.equals(client.getBaseUri())) { + client.setBaseUri(recentUri); + } + } + + private boolean isOIDCLogin(@NonNull OwnCloudAccount account) { + return (account.getCredentials() instanceof OwnCloudBearerCredentials); + } + @Nullable public OwnCloudClient removeClientFor(@Nullable OwnCloudAccount account) { diff --git a/library/src/main/java/com/owncloud/android/lib/common/OwnCloudCredentialsFactory.java b/library/src/main/java/com/owncloud/android/lib/common/OwnCloudCredentialsFactory.java index e3904440d220ae292da614355400a75eb05b1639..d053d0a4dd6779e673c19926959d363dc7c0a37b 100644 --- a/library/src/main/java/com/owncloud/android/lib/common/OwnCloudCredentialsFactory.java +++ b/library/src/main/java/com/owncloud/android/lib/common/OwnCloudCredentialsFactory.java @@ -33,6 +33,10 @@ public class OwnCloudCredentialsFactory { return new OwnCloudBasicCredentials(username, password); } + public static OwnCloudCredentials newBearerCredentials(String username, String accessToken) { + return new OwnCloudBearerCredentials(username, accessToken); + } + public static final OwnCloudCredentials getAnonymousCredentials() { if (sAnonymousCredentials == null) { sAnonymousCredentials = new OwnCloudAnonymousCredentials(); diff --git a/library/src/main/java/com/owncloud/android/lib/common/accounts/AccountUtils.java b/library/src/main/java/com/owncloud/android/lib/common/accounts/AccountUtils.java index cd95db68a12a005cd31f62aeac4e58e7e5b239f1..4a4f64c0ea18a8c526bcf0f845f5f43e3a3e37de 100644 --- a/library/src/main/java/com/owncloud/android/lib/common/accounts/AccountUtils.java +++ b/library/src/main/java/com/owncloud/android/lib/common/accounts/AccountUtils.java @@ -27,26 +27,35 @@ package com.owncloud.android.lib.common.accounts; import android.accounts.Account; import android.accounts.AccountManager; +import android.accounts.AccountManagerFuture; import android.accounts.AccountsException; import android.accounts.AuthenticatorException; import android.accounts.OperationCanceledException; +import android.app.Activity; import android.content.Context; import android.net.Uri; +import android.os.Bundle; import android.os.Looper; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.nextcloud.common.OkHttpPersistentCookieJar; import com.owncloud.android.lib.common.OwnCloudClient; import com.owncloud.android.lib.common.OwnCloudCredentials; import com.owncloud.android.lib.common.OwnCloudCredentialsFactory; import com.owncloud.android.lib.common.utils.Log_OC; import com.owncloud.android.lib.resources.status.OwnCloudVersion; +import net.openid.appauth.AuthState; + import org.apache.commons.httpclient.Cookie; +import org.json.JSONException; import java.io.IOException; +import okhttp3.CookieJar; + public class AccountUtils { private static final String TAG = AccountUtils.class.getSimpleName(); @@ -142,12 +151,62 @@ public class AccountUtils { throws OperationCanceledException, AuthenticatorException, IOException { AccountManager am = AccountManager.get(context); - String username = AccountUtils.getUsernameForAccount(account); + String username = getUsernameForAccount(account); + String accessToken = getAccessToken(am, account); + + if (accessToken != null) { + return OwnCloudCredentialsFactory.newBearerCredentials(username, accessToken); + } + String password = getPassword(am, account); return OwnCloudCredentialsFactory.newBasicCredentials(username, password); } + @NonNull + public static OwnCloudCredentials getCredentialForAccount(@NonNull Context context, @NonNull Account account, @NonNull Activity activity) throws OperationCanceledException, AuthenticatorException, IOException { + AccountManager accountManager = AccountManager.get(context); + + String username = getUsernameForAccount(account); + + String accessToken = getAccessToken(accountManager, account); + if (accessToken != null) { + return OwnCloudCredentialsFactory.newBearerCredentials(username, accessToken); + } + + AccountManagerFuture future = accountManager.getAuthToken(account, + AccountTypeUtils.getAuthTokenTypePass(account.type), null, + activity, null, null); + + Bundle result = future.getResult(); + String password = result.getString(AccountManager.KEY_AUTHTOKEN); + + if (password == null) { + password = accountManager.getPassword(account); + } + + return OwnCloudCredentialsFactory.newBasicCredentials(username, password); + } + + + @Nullable + public static String getAccessToken(@NonNull AccountManager accountManager, @NonNull Account account) { + String authStateString = accountManager.getUserData(account, Constants.KEY_AUTH_STATE); + + if (authStateString == null || authStateString.trim().isBlank()) { + return null; + } + + try { + AuthState authState = AuthState.jsonDeserialize(authStateString); + return authState.getAccessToken(); + } catch (JSONException e) { + Log_OC.e(TAG, e.getMessage()); + } + + return null; + } + @Nullable public static String getPassword(AccountManager accountManager, Account account) { String password = accountManager.getPassword(account); @@ -275,18 +334,7 @@ public class AccountUtils { public static void restoreCookies(String accountName, OwnCloudClient client, Context context) { Log_OC.d(TAG, "Restoring cookies for " + accountName); - // Account Manager - AccountManager am = AccountManager.get(context.getApplicationContext()); - - // Get account - Account account = null; - Account accounts[] = am.getAccounts(); - for (Account a : accounts) { - if (a.name.equals(accountName)) { - account = a; - break; - } - } + Account account = getAccount(context, accountName); // Restoring cookies if (account != null) { @@ -294,6 +342,34 @@ public class AccountUtils { } } + @Nullable + private static Account getAccount(@NonNull Context context, @Nullable String accountName) { + if (accountName == null) { + return null; + } + + AccountManager accountManager = AccountManager.get(context.getApplicationContext()); + + Account[] accounts = accountManager.getAccounts(); + for (Account account : accounts) { + if (account.name.equals(accountName)) { + return account; + } + } + + return null; + } + + @NonNull + public static CookieJar getOkhttpCookieJar(@NonNull Context context, @Nullable String accountName) { + Account account = getAccount(context, accountName); + if (account == null) { + return CookieJar.NO_COOKIES; + } + + return new OkHttpPersistentCookieJar(context.getApplicationContext(), account); + } + public static class AccountNotFoundException extends AccountsException { /** @@ -402,6 +478,12 @@ public class AccountUtils { * User ID, internally and never changing id of user, used by e.g. dav/trashbin/$userId/trash */ public static final String KEY_USER_ID = "oc_id"; + + public static final String KEY_AUTH_STATE = "auth_state"; + + public static final String KEY_OKHTTP_COOKIES = "cookie_key"; + + public static final String OKHTTP_COOKIE_SEPARATOR = ""; } } diff --git a/library/src/main/java/com/owncloud/android/lib/common/operations/RemoteOperation.java b/library/src/main/java/com/owncloud/android/lib/common/operations/RemoteOperation.java index 1a04b80be8c5d9b8c0dcdfb8df6652905458e603..b940244aa12530ff9d6ff78879d6b1dff17a3bda 100644 --- a/library/src/main/java/com/owncloud/android/lib/common/operations/RemoteOperation.java +++ b/library/src/main/java/com/owncloud/android/lib/common/operations/RemoteOperation.java @@ -61,6 +61,7 @@ public abstract class RemoteOperation implements Runnable { * OCS API header name */ public static final String OCS_API_HEADER = "OCS-APIREQUEST"; + public static final String OIDC_LOGIN_WITH_TOKEN = "OIDC-LOGIN-WITH-TOKEN"; public static final String OCS_ETAG_HEADER = "If-None-Match"; /**