Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 172366e6 authored by Fahim Salam Chowdhury's avatar Fahim Salam Chowdhury 👽
Browse files

feat: refactor SSO owncloud client creation logic

- to pass cookie on req, SSO owncloudClient creation logic needs to be refactored
- on account logout, owncloudClient needs to be removed from singleton map
- on account login flow, need to pass valid userId & token for murena account
parent d415360d
Loading
Loading
Loading
Loading
+10 −1
Original line number Diff line number Diff line
@@ -684,13 +684,22 @@


        <receiver
            android:name=".BootCompletedReceiver"
            android:name=".receiver.BootCompletedReceiver"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED" />
            </intent-filter>
        </receiver>

        <receiver
            android:name=".receiver.AccountRemovedReceiver"
            android:enabled="true"
            android:exported="true">
            <intent-filter>
                <action android:name="android.accounts.action.ACCOUNT_REMOVED"/>
            </intent-filter>
        </receiver>

        <!-- provider to share debug info/logs -->
        <provider
            android:name="androidx.core.content.FileProvider"
+25 −15
Original line number Diff line number Diff line
@@ -26,13 +26,13 @@ import static com.nextcloud.android.sso.Constants.EXCEPTION_UNSUPPORTED_METHOD;
import static com.nextcloud.android.sso.Constants.SSO_SHARED_PREFERENCE;

import android.accounts.Account;
import android.accounts.AccountManager;
import android.accounts.AuthenticatorException;
import android.accounts.OperationCanceledException;
import android.content.Context;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Binder;
import android.os.Build;
import android.os.ParcelFileDescriptor;
import android.text.TextUtils;

@@ -43,10 +43,10 @@ import com.nextcloud.android.sso.aidl.NextcloudRequest;
import com.nextcloud.android.sso.aidl.ParcelFileDescriptorUtil;
import com.nextcloud.android.utils.AccountManagerUtils;
import com.nextcloud.android.utils.EncryptionUtils;
import com.owncloud.android.lib.common.OwnCloudAccount;
import com.owncloud.android.lib.common.OwnCloudClient;
import com.owncloud.android.lib.common.OwnCloudClientFactory;
import com.owncloud.android.lib.common.OwnCloudCredentialsFactory;
import com.owncloud.android.lib.common.accounts.AccountUtils;
import com.owncloud.android.lib.common.OwnCloudClientManager;
import com.owncloud.android.lib.common.OwnCloudClientManagerFactory;

import org.apache.commons.httpclient.HttpConnection;
import org.apache.commons.httpclient.HttpMethodBase;
@@ -74,11 +74,14 @@ import java.io.InputStreamReader;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.logging.Level;

import at.bitfire.davdroid.BuildConfig;
import at.bitfire.davdroid.log.Logger;

public class InputStreamBinder extends IInputStreamService.Stub {
@@ -333,9 +336,10 @@ public class InputStreamBinder extends IInputStreamService.Stub {
                    new IllegalStateException("URL need to start with a /"));
        }

        Uri serverUri = Uri.parse(AccountUtils.getBaseUrlForAccount(context, account));
        OwnCloudClient client = OwnCloudClientFactory.createOwnCloudClient(serverUri, context, true);
        client.setCredentials(OwnCloudCredentialsFactory.newBasicCredentials(account.name, getAcountPwd(account, context)));
        OwnCloudClientManagerFactory.setUserAgent(getUserAgent());
        OwnCloudClientManager ownCloudClientManager = OwnCloudClientManagerFactory.getDefaultSingleton();
        OwnCloudAccount ocAccount = new OwnCloudAccount(account, context);
        OwnCloudClient client = ownCloudClientManager.getClientFor(ocAccount, context);

        HttpMethodBase method = buildMethod(request, client.getBaseUri(), requestBodyInputStream);

@@ -360,6 +364,8 @@ public class InputStreamBinder extends IInputStreamService.Stub {
        client.setFollowRedirects(true);
        int status = client.executeMethod(method);

        ownCloudClientManager.saveAllClients(context, account.type);

        // Check if status code is 2xx --> https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#2xx_Success
        if (status >= HTTP_STATUS_CODE_OK && status < HTTP_STATUS_CODE_MULTIPLE_CHOICES) {
            return method;
@@ -380,6 +386,12 @@ public class InputStreamBinder extends IInputStreamService.Stub {
        }
    }

    private static String getUserAgent() {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyMMdd", Locale.getDefault());
        String time = simpleDateFormat.format(Build.TIME);
        return "eos(" + time + ")-AccountManager-SSO(" + BuildConfig.VERSION_NAME + ")";
    }

    private Response processRequestV2(final NextcloudRequest request, final InputStream requestBodyInputStream)
            throws UnsupportedOperationException,
            com.owncloud.android.lib.common.accounts.AccountUtils.AccountNotFoundException,
@@ -400,9 +412,10 @@ public class InputStreamBinder extends IInputStreamService.Stub {
                    new IllegalStateException("URL need to start with a /"));
        }

        Uri serverUri = Uri.parse(AccountUtils.getBaseUrlForAccount(context, account));
        OwnCloudClient client = OwnCloudClientFactory.createOwnCloudClient(serverUri, context, true);
        client.setCredentials(OwnCloudCredentialsFactory.newBasicCredentials(account.name, getAcountPwd(account, context)));
        OwnCloudClientManagerFactory.setUserAgent(getUserAgent());
        OwnCloudClientManager ownCloudClientManager = OwnCloudClientManagerFactory.getDefaultSingleton();
        OwnCloudAccount ocAccount = new OwnCloudAccount(account, context);
        OwnCloudClient client = ownCloudClientManager.getClientFor(ocAccount, context);

        HttpMethodBase method = buildMethod(request, client.getBaseUri(), requestBodyInputStream);

@@ -428,6 +441,8 @@ public class InputStreamBinder extends IInputStreamService.Stub {
        client.setFollowRedirects(true);
        int status = client.executeMethod(method);

        ownCloudClientManager.saveAllClients(context, account.type);

        // Check if status code is 2xx --> https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#2xx_Success
        if (status >= HTTP_STATUS_CODE_OK && status < HTTP_STATUS_CODE_MULTIPLE_CHOICES) {
            return new Response(method);
@@ -447,11 +462,6 @@ public class InputStreamBinder extends IInputStreamService.Stub {
        }
    }

    private static String getAcountPwd(Account account, Context ctx) throws AccountUtils.AccountNotFoundException {
        return AccountManager.get(ctx).getPassword(account);
    }


    private boolean isValid(NextcloudRequest request) {
        String callingPackageName = context.getPackageManager().getNameForUid(Binder.getCallingUid());

+2 −19
Original line number Diff line number Diff line
@@ -35,7 +35,6 @@ import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;

@@ -49,6 +48,7 @@ import java.util.logging.Level;

import at.bitfire.davdroid.R;
import at.bitfire.davdroid.log.Logger;
import at.bitfire.davdroid.util.SSOUtils;

public class SsoGrantPermissionActivity extends AppCompatActivity {

@@ -115,7 +115,7 @@ public class SsoGrantPermissionActivity extends AppCompatActivity {

        // create token
        String token = UUID.randomUUID().toString().replaceAll("-", "");
        String userId = sanitizeUserId(account.name);
        String userId = SSOUtils.INSTANCE.sanitizeUserId(account.name);

        saveToken(token, account.name);
        setResultData(token, userId, serverUrl);
@@ -136,23 +136,6 @@ public class SsoGrantPermissionActivity extends AppCompatActivity {
        setResult(RESULT_OK, data);
    }

    /**
     * Murena account's userId is set same as it's email address.
     * For old accounts (@e.email) userId = email.
     * For new accounts (@murena.io) userId is first part of email (ex: for email abc@murena.io, userId is abc).
     * For api requests, we needed to pass the actual userId. This method remove the unwanted part (@murena.io) from the userId
     */
    @NonNull
    private static String sanitizeUserId(@NonNull String userId) {
        final String murenaMailEndPart = "@murena.io";

        if (!userId.endsWith(murenaMailEndPart)) {
            return userId;
        }

        return userId.split(murenaMailEndPart)[0];
    }

    @Nullable
    private String getServerUrl() {
        try {
+69 −0
Original line number Diff line number Diff line
/*
 * 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 <https://www.gnu.org/licenses/>.
 */

package at.bitfire.davdroid.receiver

import android.accounts.Account
import android.accounts.AccountManager
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import at.bitfire.davdroid.log.Logger
import at.bitfire.davdroid.syncadapter.AccountUtils
import com.owncloud.android.lib.common.OwnCloudAccount
import com.owncloud.android.lib.common.OwnCloudClientManagerFactory
import com.owncloud.android.lib.common.accounts.AccountUtils.AccountNotFoundException

class AccountRemovedReceiver : BroadcastReceiver() {

    companion object {
        private const val ACCOUNT_REMOVAL_ACTION = "android.accounts.action.ACCOUNT_REMOVED"
    }

    override fun onReceive(context: Context?, intent: Intent?) {
        if (context == null || intent == null || intent.action != ACCOUNT_REMOVAL_ACTION) {
            return
        }

        val account = getAccount(context, intent) ?: return

        val ownCloudClientManager = OwnCloudClientManagerFactory.getDefaultSingleton()
        try {
            val ocAccount = OwnCloudAccount(account, context)
            ownCloudClientManager.removeClientFor(ocAccount)
        } catch (e: AccountNotFoundException) {
            Logger.log.warning("exception thrown as account not found. Mostly because not NC account. ${e.localizedMessage}")
        }
    }

    private fun getAccount(context: Context, intent: Intent): Account? {
        val accountType = intent.extras?.getString(AccountManager.KEY_ACCOUNT_TYPE)
        if (accountType !in AccountUtils.getMainAccountTypes(context)) {
            return null
        }

        val accountName = intent.extras?.getString(AccountManager.KEY_ACCOUNT_NAME) ?: return null

        val accounts = AccountManager.get(context).getAccountsByType(accountType)
        for (account in accounts) {
            if (account.name == accountName) {
                return account
            }
        }

        return null
    }
}
+1 −1
Original line number Diff line number Diff line
@@ -2,7 +2,7 @@
 * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
 **************************************************************************************************/

package at.bitfire.davdroid
package at.bitfire.davdroid.receiver

import android.content.BroadcastReceiver
import android.content.Context
Loading