Skip to content

Commit 1d588e7

Browse files
Merge pull request #735 from nextcloud/dynamicSSO
Dynamic SSO
2 parents 199aafd + 3a17dc7 commit 1d588e7

File tree

5 files changed

+127
-55
lines changed

5 files changed

+127
-55
lines changed

lib/src/main/java/com/nextcloud/android/sso/AccountImporter.java

Lines changed: 15 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,6 @@
99
*/
1010
package com.nextcloud.android.sso;
1111

12-
import static android.app.Activity.RESULT_CANCELED;
13-
import static android.app.Activity.RESULT_OK;
14-
import static com.nextcloud.android.sso.Constants.NEXTCLOUD_FILES_ACCOUNT;
15-
import static com.nextcloud.android.sso.Constants.NEXTCLOUD_SSO;
16-
import static com.nextcloud.android.sso.Constants.NEXTCLOUD_SSO_EXCEPTION;
17-
import static com.nextcloud.android.sso.Constants.SSO_SHARED_PREFERENCE;
18-
1912
import android.Manifest;
2013
import android.accounts.Account;
2114
import android.accounts.AccountManager;
@@ -31,10 +24,6 @@
3124
import android.util.Log;
3225
import android.widget.Toast;
3326

34-
import androidx.core.app.ActivityCompat;
35-
import androidx.core.content.ContextCompat;
36-
import androidx.fragment.app.Fragment;
37-
3827
import com.nextcloud.android.sso.exceptions.AccountImportCancelledException;
3928
import com.nextcloud.android.sso.exceptions.AndroidGetAccountsPermissionNotGranted;
4029
import com.nextcloud.android.sso.exceptions.NextcloudFilesAppAccountNotFoundException;
@@ -43,18 +32,26 @@
4332
import com.nextcloud.android.sso.exceptions.NextcloudFilesAppNotSupportedException;
4433
import com.nextcloud.android.sso.exceptions.SSOException;
4534
import com.nextcloud.android.sso.exceptions.UnknownErrorException;
46-
import com.nextcloud.android.sso.model.FilesAppType;
4735
import com.nextcloud.android.sso.model.SingleSignOnAccount;
4836
import com.nextcloud.android.sso.ui.UiExceptionManager;
4937

5038
import java.io.IOException;
5139
import java.util.ArrayList;
52-
import java.util.Arrays;
5340
import java.util.List;
5441

42+
import androidx.core.app.ActivityCompat;
43+
import androidx.core.content.ContextCompat;
44+
import androidx.fragment.app.Fragment;
5545
import io.reactivex.annotations.NonNull;
5646
import io.reactivex.annotations.Nullable;
5747

48+
import static android.app.Activity.RESULT_CANCELED;
49+
import static android.app.Activity.RESULT_OK;
50+
import static com.nextcloud.android.sso.Constants.NEXTCLOUD_FILES_ACCOUNT;
51+
import static com.nextcloud.android.sso.Constants.NEXTCLOUD_SSO;
52+
import static com.nextcloud.android.sso.Constants.NEXTCLOUD_SSO_EXCEPTION;
53+
import static com.nextcloud.android.sso.Constants.SSO_SHARED_PREFERENCE;
54+
5855
public class AccountImporter {
5956

6057
private static final String TAG = AccountImporter.class.getCanonicalName();
@@ -67,8 +64,6 @@ public class AccountImporter {
6764

6865
private static SharedPreferences SHARED_PREFERENCES;
6966

70-
private static final String[] ACCOUNT_TYPES = Arrays.stream(FilesAppType.values()).map(a -> a.accountType).toArray(String[]::new);
71-
7267
public static boolean accountsToImportAvailable(Context context) {
7368
return findAccounts(context).size() > 0;
7469
}
@@ -78,7 +73,7 @@ public static void pickNewAccount(Activity activity) throws NextcloudFilesAppNot
7873
checkAndroidAccountPermissions(activity);
7974

8075
if (appInstalledOrNot(activity)) {
81-
Intent intent = AccountManager.newChooseAccountIntent(null, null, ACCOUNT_TYPES,
76+
Intent intent = AccountManager.newChooseAccountIntent(null, null, FilesAppTypeRegistry.getInstance().getAccountTypes(),
8277
true, null, AUTH_TOKEN_SSO, null, null);
8378
activity.startActivityForResult(intent, CHOOSE_ACCOUNT_SSO);
8479
} else {
@@ -91,7 +86,7 @@ public static void pickNewAccount(Fragment fragment) throws NextcloudFilesAppNot
9186
checkAndroidAccountPermissions(fragment.getContext());
9287

9388
if (appInstalledOrNot(fragment.requireContext())) {
94-
Intent intent = AccountManager.newChooseAccountIntent(null, null, ACCOUNT_TYPES,
89+
Intent intent = AccountManager.newChooseAccountIntent(null, null, FilesAppTypeRegistry.getInstance().getAccountTypes(),
9590
true, null, AUTH_TOKEN_SSO, null, null);
9691
fragment.startActivityForResult(intent, CHOOSE_ACCOUNT_SSO);
9792
} else {
@@ -123,7 +118,7 @@ private static void checkAndroidAccountPermissions(Context context) throws Andro
123118
private static boolean appInstalledOrNot(Context context) {
124119
boolean returnValue = false;
125120
PackageManager pm = context.getPackageManager();
126-
for (final var appType : FilesAppType.values()) {
121+
for (final var appType : FilesAppTypeRegistry.getInstance().getTypes()) {
127122
try {
128123
pm.getPackageInfo(appType.packageId, PackageManager.GET_ACTIVITIES);
129124
returnValue = true;
@@ -142,7 +137,7 @@ public static List<Account> findAccounts(final Context context) {
142137

143138
List<Account> accountsAvailable = new ArrayList<>();
144139
for (final Account account : accounts) {
145-
for (String accountType : ACCOUNT_TYPES) {
140+
for (String accountType : FilesAppTypeRegistry.getInstance().getAccountTypes()) {
146141
if (accountType.equals(account.type)) {
147142
accountsAvailable.add(account);
148143
}
@@ -370,7 +365,7 @@ private static Intent buildRequestAuthTokenIntent(Context context, Intent intent
370365
throw new NextcloudFilesAppAccountPermissionNotGrantedException(context);
371366
}
372367

373-
String componentName = FilesAppType.findByAccountType(account.type).packageId;
368+
String componentName = FilesAppTypeRegistry.getInstance().findByAccountType(account.type).packageId;
374369

375370
Intent authIntent = new Intent();
376371
authIntent.setComponent(new ComponentName(componentName,
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/*
2+
* Nextcloud Android SingleSignOn Library
3+
*
4+
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
5+
* SPDX-FileCopyrightText: 2024 Tobias Kaminsky <[email protected]>
6+
* SPDX-License-Identifier: GPL-3.0-or-later
7+
*/
8+
package com.nextcloud.android.sso;
9+
10+
import com.nextcloud.android.sso.model.FilesAppType;
11+
12+
import java.util.HashSet;
13+
import java.util.List;
14+
import java.util.Optional;
15+
import java.util.Set;
16+
17+
import androidx.annotation.NonNull;
18+
import androidx.annotation.Nullable;
19+
20+
public class FilesAppTypeRegistry {
21+
private static final FilesAppTypeRegistry FILES_APP_TYPE_REGISTRY = new FilesAppTypeRegistry();
22+
private final Set<FilesAppType> types = new HashSet<>();
23+
24+
public FilesAppTypeRegistry() {
25+
types.add(new FilesAppType("com.nextcloud.client", "nextcloud", FilesAppType.Type.PROD));
26+
types.add(new FilesAppType("com.nextcloud.android.qa", "nextcloud.qa", FilesAppType.Type.QA));
27+
types.add(new FilesAppType("com.nextcloud.android.beta", "nextcloud.beta", FilesAppType.Type.DEV));
28+
}
29+
30+
public static FilesAppTypeRegistry getInstance() {
31+
return FILES_APP_TYPE_REGISTRY;
32+
}
33+
34+
public synchronized void init(FilesAppType type) {
35+
types.clear();
36+
37+
if (type.type != FilesAppType.Type.PROD) {
38+
throw new IllegalArgumentException("If only one FilesAppType added, this must be PROD!");
39+
}
40+
41+
types.add(type);
42+
}
43+
44+
public synchronized void init(List<FilesAppType> types) {
45+
this.types.clear();
46+
47+
Optional<FilesAppType> prod = types.stream().filter(t -> t.type == FilesAppType.Type.PROD).findFirst();
48+
if (prod.isEmpty()) {
49+
throw new IllegalArgumentException("One provided FilesAppType must be PROD!");
50+
}
51+
52+
this.types.addAll(types);
53+
}
54+
55+
public Set<FilesAppType> getTypes() {
56+
return types;
57+
}
58+
59+
public String[] getAccountTypes() {
60+
return types.stream().map(a -> a.accountType).toArray(String[]::new);
61+
}
62+
63+
64+
/**
65+
* @return {@link FilesAppType.Type#PROD}, {@link FilesAppType.Type#QA}
66+
* or {@link FilesAppType.Type#DEV} depending on {@param accountType}.
67+
* Uses {@link FilesAppType.Type#PROD} as fallback.
68+
*/
69+
@NonNull
70+
public FilesAppType findByAccountType(@Nullable String accountType) {
71+
for (final var type : types) {
72+
if (type.accountType.equalsIgnoreCase(accountType)) {
73+
return type;
74+
}
75+
}
76+
return types.stream().filter(t -> t.type == FilesAppType.Type.PROD).findFirst().get();
77+
}
78+
}

lib/src/main/java/com/nextcloud/android/sso/api/AidlNetworkRequest.java

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,6 @@
99
*/
1010
package com.nextcloud.android.sso.api;
1111

12-
import static com.nextcloud.android.sso.aidl.ParcelFileDescriptorUtil.pipeFrom;
13-
import static com.nextcloud.android.sso.exceptions.SSOException.parseNextcloudCustomException;
14-
1512
import android.content.ComponentName;
1613
import android.content.Context;
1714
import android.content.Intent;
@@ -23,15 +20,12 @@
2320
import android.os.RemoteException;
2421
import android.util.Log;
2522

26-
import androidx.annotation.NonNull;
27-
import androidx.annotation.Nullable;
28-
2923
import com.google.gson.Gson;
3024
import com.google.gson.internal.LinkedTreeMap;
25+
import com.nextcloud.android.sso.FilesAppTypeRegistry;
3126
import com.nextcloud.android.sso.aidl.IInputStreamService;
3227
import com.nextcloud.android.sso.aidl.NextcloudRequest;
3328
import com.nextcloud.android.sso.exceptions.NextcloudApiNotRespondingException;
34-
import com.nextcloud.android.sso.model.FilesAppType;
3529
import com.nextcloud.android.sso.model.SingleSignOnAccount;
3630

3731
import java.io.ByteArrayInputStream;
@@ -44,6 +38,12 @@
4438
import java.util.ArrayList;
4539
import java.util.concurrent.atomic.AtomicBoolean;
4640

41+
import androidx.annotation.NonNull;
42+
import androidx.annotation.Nullable;
43+
44+
import static com.nextcloud.android.sso.aidl.ParcelFileDescriptorUtil.pipeFrom;
45+
import static com.nextcloud.android.sso.exceptions.SSOException.parseNextcloudCustomException;
46+
4747
public class AidlNetworkRequest extends NetworkRequest {
4848
private static final String TAG = AidlNetworkRequest.class.getCanonicalName();
4949

@@ -91,7 +91,7 @@ public void connect(String type) {
9191
Log.d(TAG, "[connect] Binding to AccountManagerService for type [" + type + "]");
9292
super.connect(type);
9393

94-
final String componentName = FilesAppType.findByAccountType(type).packageId;
94+
final String componentName = FilesAppTypeRegistry.getInstance().findByAccountType(type).packageId;
9595

9696
Log.d(TAG, "[connect] Component name is: [" + componentName + "]");
9797

lib/src/main/java/com/nextcloud/android/sso/helper/VersionCheckHelper.java

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,16 @@
1212
import android.content.pm.PackageManager;
1313
import android.util.Log;
1414

15-
import androidx.annotation.NonNull;
16-
15+
import com.nextcloud.android.sso.FilesAppTypeRegistry;
1716
import com.nextcloud.android.sso.exceptions.NextcloudFilesAppNotInstalledException;
1817
import com.nextcloud.android.sso.exceptions.NextcloudFilesAppNotSupportedException;
1918
import com.nextcloud.android.sso.model.FilesAppType;
2019
import com.nextcloud.android.sso.ui.UiExceptionManager;
2120

21+
import java.util.Optional;
22+
23+
import androidx.annotation.NonNull;
24+
2225
public final class VersionCheckHelper {
2326

2427
private static final String TAG = VersionCheckHelper.class.getCanonicalName();
@@ -38,12 +41,22 @@ public static boolean verifyMinVersion(@NonNull Context context, int minVersion,
3841

3942
// Stable Files App is not installed at all. Therefore we need to run the test on the dev app
4043
try {
41-
final int verCode = getNextcloudFilesVersionCode(context, FilesAppType.DEV);
42-
// The dev app follows a different versioning schema.. therefore we can't do our normal checks
44+
Optional<FilesAppType> dev = FilesAppTypeRegistry
45+
.getInstance()
46+
.getTypes()
47+
.stream()
48+
.filter(t -> t.type == FilesAppType.Type.DEV)
49+
.findFirst();
50+
if (dev.isPresent()) {
51+
final int verCode = getNextcloudFilesVersionCode(context, dev.get());
52+
// The dev app follows a different versioning schema.. therefore we can't do our normal checks
4353

44-
// However beta users are probably always up to date so we will just ignore it for now
45-
Log.d(TAG, "Dev files app version is: " + verCode);
46-
return true;
54+
// However beta users are probably always up to date so we will just ignore it for now
55+
Log.d(TAG, "Dev files app version is: " + verCode);
56+
return true;
57+
} else {
58+
UiExceptionManager.showDialogForException(context, new NextcloudFilesAppNotInstalledException(context));
59+
}
4760
} catch (PackageManager.NameNotFoundException ex) {
4861
Log.e(TAG, "PackageManager.NameNotFoundException (dev files app not found): " + e.getMessage());
4962
UiExceptionManager.showDialogForException(context, new NextcloudFilesAppNotInstalledException(context));

lib/src/main/java/com/nextcloud/android/sso/model/FilesAppType.java

Lines changed: 6 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -8,33 +8,19 @@
88
package com.nextcloud.android.sso.model;
99

1010
import androidx.annotation.NonNull;
11-
import androidx.annotation.Nullable;
12-
13-
public enum FilesAppType {
14-
15-
PROD("com.nextcloud.client", "nextcloud"),
16-
QA("com.nextcloud.android.qa", "nextcloud.qa"),
17-
DEV("com.nextcloud.android.beta", "nextcloud.beta");
1811

12+
public class FilesAppType {
1913
public final String packageId;
2014
public final String accountType;
15+
public final Type type;
2116

22-
FilesAppType(@NonNull String packageId, @NonNull String accountType) {
17+
public FilesAppType(@NonNull String packageId, @NonNull String accountType, Type type) {
2318
this.packageId = packageId;
2419
this.accountType = accountType;
20+
this.type = type;
2521
}
2622

27-
/**
28-
* @return {@link #PROD}, {@link #QA} or {@link #DEV} depending on {@param accountType}.
29-
* Uses {@link #PROD} as fallback.
30-
*/
31-
@NonNull
32-
public static FilesAppType findByAccountType(@Nullable String accountType) {
33-
for (final var appType : FilesAppType.values()) {
34-
if (appType.accountType.equalsIgnoreCase(accountType)) {
35-
return appType;
36-
}
37-
}
38-
return PROD;
23+
public enum Type {
24+
PROD, QA, DEV
3925
}
4026
}

0 commit comments

Comments
 (0)