From c9cac050da5d23ffeb08fbc57c208e52620f3bc4 Mon Sep 17 00:00:00 2001 From: Jan Kobersky Date: Tue, 10 Sep 2024 13:12:39 +0200 Subject: [PATCH 1/5] Squashed commit of the following: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit 217bdb0f4e6be0852aa596e9a65acb5d89a0a0dd Author: Juraj Ďurech Date: Tue Sep 10 12:46:41 2024 +0200 Version 2.5.2 commit acf6798f86fc9cd7ae64dca192cd88f23e9af570 Author: Juraj Ďurech Date: Tue Sep 10 12:46:11 2024 +0200 Fixed optional sharedMemoryIdentifier in PowerAuthSharingConfiguration (#199) commit 9219d23304954a2283215ddd8c095e953c8b34ce Author: Juraj Ďurech <1719814+hvge@users.noreply.github.com> Date: Tue Sep 10 11:53:15 2024 +0200 Added support for iOS activation data sharing config (#201) * Fix #199: Added support for iOS activation data sharing config --- android/build.gradle | 2 +- .../reactnative/PowerAuthModule.java | 8 +- docs/Configuration.md | 18 ++- ios/Podfile | 2 +- ios/PowerAuth/PowerAuthModule.m | 29 ++++- package.json | 2 +- react-native-powerauth-mobile-sdk.podspec | 2 +- src/PowerAuth.ts | 29 ++++- src/index.ts | 2 + src/model/PowerAuthError.ts | 9 ++ .../PowerAuthExternalPendingOperation.ts | 41 +++++++ src/model/PowerAuthSharingConfiguration.ts | 104 ++++++++++++++++++ testapp/_tests/PowerAuth_Activation.test.ts | 1 + testapp/_tests/PowerAuth_Configure.test.ts | 31 ++++++ testapp/_tests/helpers/RNActivationHelper.ts | 10 +- testapp/ios/testapp.xcodeproj/project.pbxproj | 4 + testapp/ios/testapp/testapp.entitlements | 10 ++ 17 files changed, 292 insertions(+), 12 deletions(-) create mode 100644 src/model/PowerAuthExternalPendingOperation.ts create mode 100644 src/model/PowerAuthSharingConfiguration.ts create mode 100644 testapp/ios/testapp/testapp.entitlements diff --git a/android/build.gradle b/android/build.gradle index 63fcb98..9b61830 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -69,7 +69,7 @@ repositories { } dependencies { - api "com.wultra.android.powerauth:powerauth-sdk:1.7.8" + api "com.wultra.android.powerauth:powerauth-sdk:1.7.9" if (project == rootProject) { // The standalone build require to specify exact version of RN diff --git a/android/src/main/java/com/wultra/android/powerauth/reactnative/PowerAuthModule.java b/android/src/main/java/com/wultra/android/powerauth/reactnative/PowerAuthModule.java index b32f977..3171c3c 100644 --- a/android/src/main/java/com/wultra/android/powerauth/reactnative/PowerAuthModule.java +++ b/android/src/main/java/com/wultra/android/powerauth/reactnative/PowerAuthModule.java @@ -96,7 +96,7 @@ public void isConfigured(@Nonnull String instanceId, final Promise promise) { } @ReactMethod - public void configure(final String instanceId, final ReadableMap configuration, final ReadableMap clientConfiguration, final ReadableMap biometryConfiguration, final ReadableMap keychainConfiguration, final Promise promise) { + public void configure(final String instanceId, final ReadableMap configuration, final ReadableMap clientConfiguration, final ReadableMap biometryConfiguration, final ReadableMap keychainConfiguration, final ReadableMap sharingConfiguration, Promise promise) { try { boolean result = registerPowerAuthInstance(instanceId, () -> { // Create configurations from maps @@ -288,6 +288,12 @@ public void run(@NonNull PowerAuthSDK sdk) { }); } + @ReactMethod + public void getExternalPendingOperation(String instanceId, final Promise promise) { + // Not supported on Android + promise.resolve(null); + } + @ReactMethod public void fetchActivationStatus(String instanceId, final Promise promise) { final Context context = this.context; diff --git a/docs/Configuration.md b/docs/Configuration.md index 6b9ea5b..075513f 100644 --- a/docs/Configuration.md +++ b/docs/Configuration.md @@ -70,6 +70,14 @@ In case that you need an advanced configuration, then you can import and use the - `userDefaultsSuiteName` - iOS specific, defines suite name used by the `UserDefaults` that check for Keychain data presence. This is useful in situations, when your application is sharing data with another application or application's extension from the same vendor. The default value is `null`. See note2 below. - `minimalRequiredKeychainProtection` - Android specific, defines minimal required keychain protection level that must be supported on the current device. The default value is `PowerAuthKeychainProtection.NONE`. See note3 below. +- `PowerAuthSharingConfiguration` class or `PowerAuthSharingConfigurationType` interface - to configure an activation data sharing on iOS platform. You can alter the following parameters: + - `appGroup` - defines name of app group that allows you sharing data between multiple applications. Be aware that the value overrides `accessGroupName` property if it's provided in `PowerAuthKeychainConfiguration`. + - `appIdentifier`- defines unique application identifier. This identifier helps you to determine which application currently holds the lock on activation data in a special operations. + - `keychainAccessGroup` - defines keychain access group name used by the PowerAuthSDK keychain instances. + - `sharedMemoryIdentifier` - defines optional identifier of memory shared between the applications in app group. If identifier is not provided then PowerAuthSDK calculate unique identifier based on `PowerAuth.instanceId`. + - If you're not familiar with sharing data between iOS applications, or app extensions, then please refer the native PowerAuth mobile SDK documentation, where this topic is explained in more detail. + + > Note 1: Setting `authenticateOnBiometricKeySetup` parameter to `true` leads to use symmetric AES cipher on the background so both configuration and usage of biometric key require the biometric authentication. If set to `false`, then RSA cipher is used and only the usage of biometric key require the biometric authentication. This is due to fact, that RSA cipher can encrypt data with using it's public key available immediate after the key-pair is created in Android KeyStore. > Note 2: You're responsible to migrate the keychain and `UserDefaults` data from non-shared storage to the shared one, before you configure the first `PowerAuth` instance. This is quite difficult to do in JavaScript, so it's recommended to do not alter `PowerAuthKeychainConfiguration` once your application is already shipped in AppStore. @@ -100,7 +108,15 @@ export default class AppMyApplication extends Component { const clientConfiguration = { enableUnsecureTraffic: false }; const biometryConfiguration = { linkItemsToCurrentSet: true }; const keychainConfiguration = { minimalRequiredKeychainProtection: PowerAuthKeychainProtection.SOFTWARE }; - await this.powerAuth.configure(configuration, clientConfiguration, biometryConfiguration, keychainConfiguration); + const sharingConfiguration = { + // This is iOS specific. All values will be ignored on Android platform. + // All the following values are fake. Please read a native PowerAuth mobile SDK documentation + // about activation data sharing that explains how to prepare parameters in detail. + appGroup: "group.your.app.group", + appIdentifier: "some.identifier", + keychainAccessGroup: "keychain.access.group" + }; + await this.powerAuth.configure(configuration, clientConfiguration, biometryConfiguration, keychainConfiguration, sharingConfiguration); console.log("PowerAuth configuration successfull."); } catch(e) { console.log(`PowerAuth failed to configure: ${e.code}`); diff --git a/ios/Podfile b/ios/Podfile index 4ae1ee8..27c2e43 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -11,7 +11,7 @@ target 'PowerAuth' do :path => config[:reactNativePath], :hermes_enabled => false ) - pod 'PowerAuth2', '~> 1.7.8' + pod 'PowerAuth2', '~> 1.7.9' # Uncomment to use not-published SDK in project. This is effective only if you manually open 'ios/PowerAuth.xcworkspace' #pod 'PowerAuth2', :git => 'https://github.com/wultra/powerauth-mobile-sdk.git', :branch => 'develop', :submodules => true end diff --git a/ios/PowerAuth/PowerAuthModule.m b/ios/PowerAuth/PowerAuthModule.m index 5a1aaac..82260f6 100644 --- a/ios/PowerAuth/PowerAuthModule.m +++ b/ios/PowerAuth/PowerAuthModule.m @@ -69,7 +69,8 @@ + (BOOL) requiresMainQueueSetup PAJS_ARGUMENT(configuration, NSDictionary*) PAJS_ARGUMENT(clientConfiguration, NSDictionary*) PAJS_ARGUMENT(biometryConfiguration, NSDictionary*) - PAJS_ARGUMENT(keychainConfiguration, NSDictionary*)) + PAJS_ARGUMENT(keychainConfiguration, NSDictionary*) + PAJS_ARGUMENT(sharingConfiguration, NSDictionary*)) { if (![self validateInstanceId:instanceId reject:reject]) { return; @@ -82,6 +83,14 @@ + (BOOL) requiresMainQueueSetup config.appSecret = CAST_TO(configuration[@"applicationSecret"], NSString); config.masterServerPublicKey = CAST_TO(configuration[@"masterServerPublicKey"], NSString); config.baseEndpointUrl = CAST_TO(configuration[@"baseEndpointUrl"], NSString); + // Prepare sharing configuration + if (CAST_TO(sharingConfiguration[@"isProvided"], NSNumber).boolValue) { + PowerAuthSharingConfiguration * sharingConfig = [[PowerAuthSharingConfiguration alloc] initWithAppGroup:CAST_TO(sharingConfiguration[@"appGroup"], NSString) + appIdentifier:CAST_TO(sharingConfiguration[@"appIdentifier"], NSString) + keychainAccessGroup:CAST_TO(sharingConfiguration[@"keychainAccessGroup"], NSString)]; + sharingConfig.sharedMemoryIdentifier = CAST_TO(sharingConfiguration[@"sharedMemoryIdentifier"], NSString); + config.sharingConfiguration = sharingConfig; + } if (![config validateConfiguration]) { reject(EC_WRONG_PARAMETER, @"Provided configuration is invalid", nil); @@ -182,6 +191,24 @@ + (BOOL) requiresMainQueueSetup } PAJS_METHOD_END +RCT_REMAP_METHOD(getExternalPendingOperation, + instanceId:(NSString*)instanceId + getExternalPendingOperationResolve:(RCTPromiseResolveBlock)resolve + getExternalPendingOperationReject:(RCTPromiseRejectBlock)reject) +{ + PA_BLOCK_START + PowerAuthExternalPendingOperation * pendingOperation = powerAuth.externalPendingOperation; + if (pendingOperation) { + resolve(@{ + @"externalOperationType": pendingOperation.externalOperationType == PowerAuthExternalPendingOperationType_Activation ? @"ACTIVATION" : @"PROTOCOL_UPGRADE", + @"externalApplicationId": pendingOperation.externalApplicationId + }); + } else { + resolve(nil); + } + PA_BLOCK_END +} + PAJS_METHOD_START(fetchActivationStatus, PAJS_ARGUMENT(instanceId, NSString*)) { diff --git a/package.json b/package.json index ac21b82..325f7fb 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "react-native-powerauth-mobile-sdk", "title": "PowerAuth SDK for React Native Mobile Apps", - "version": "2.5.0", + "version": "2.5.2", "description": "Wultra PowerAuth Mobile SDK React Native component for iOS and Android", "main": "lib/index.js", "types": "lib/index.d.ts", diff --git a/react-native-powerauth-mobile-sdk.podspec b/react-native-powerauth-mobile-sdk.podspec index 79f4b81..c2ae364 100644 --- a/react-native-powerauth-mobile-sdk.podspec +++ b/react-native-powerauth-mobile-sdk.podspec @@ -17,7 +17,7 @@ Pod::Spec.new do |s| s.requires_arc = true s.dependency "React-Core" - s.dependency "PowerAuth2", "~> 1.7.8" + s.dependency "PowerAuth2", "~> 1.7.9" # Don't install the dependencies when we run `pod install` in the old architecture. if ENV['RCT_NEW_ARCH_ENABLED'] == '1' then diff --git a/src/PowerAuth.ts b/src/PowerAuth.ts index aad9dfb..c6b6622 100644 --- a/src/PowerAuth.ts +++ b/src/PowerAuth.ts @@ -34,6 +34,8 @@ import { AuthResolver } from "./internal/AuthResolver"; import { PasswordType, PowerAuthPassword } from './model/PowerAuthPassword'; import { PowerAuthActivationCodeUtil } from './PowerAuthActivationCodeUtil'; import { RawAuthentication, toRawPassword } from './internal/NativeTypes'; +import { buildSharingConfiguration, PowerAuthSharingConfigurationType } from './model/PowerAuthSharingConfiguration'; +import { PowerAuthExternalPendingOperation } from './model/PowerAuthExternalPendingOperation'; /** * Class used for the main interaction with the PowerAuth SDK components. @@ -63,6 +65,12 @@ export class PowerAuth { get keychainConfiguration(): PowerAuthKeychainConfigurationType | undefined { return configRegister.get(this.instanceId)?.keychainConfiguration } + /** + * Sharing configuration used to configure this instance of class. + */ + get sharingConfiguration(): PowerAuthSharingConfigurationType | undefined { + return configRegister.get(this.instanceId)?.sharingConfiguration + } /** * Object for managing access tokens. @@ -95,12 +103,14 @@ export class PowerAuth { * @param clientConfiguration Configuration for internal HTTP client. If `undefined`, then the default configuration is used. * @param biometryConfiguration Biometry configuration. If `undefined`, then the default configuration is used. * @param keychainConfiguration Configuration for internal keychain storage. If `undefined`, then the default configuration is used. + * @param sharingConfiguration Configuration for iOS activation data sharing. If `undefined`, then no sharing configuration is applied. */ configure( configuration: PowerAuthConfigurationType, clientConfiguration?: PowerAuthClientConfigurationType, biometryConfiguration?: PowerAuthBiometryConfigurationType, - keychainConfiguration?: PowerAuthKeychainConfigurationType + keychainConfiguration?: PowerAuthKeychainConfigurationType, + sharingConfiguration?: PowerAuthSharingConfigurationType ): Promise; /** @@ -127,6 +137,7 @@ export class PowerAuth { let clientConfiguration: PowerAuthClientConfigurationType let biometryConfiguration: PowerAuthBiometryConfigurationType let keychainConfiguration: PowerAuthKeychainConfigurationType + let sharingConfiguration: PowerAuthSharingConfigurationType if (typeof param1 === 'string') { configuration = buildConfiguration({ applicationKey: param1, @@ -136,19 +147,22 @@ export class PowerAuth { clientConfiguration = buildClientConfiguration({enableUnsecureTraffic: args[3]}) biometryConfiguration = buildBiometryConfiguration() keychainConfiguration = buildKeychainConfiguration() + sharingConfiguration = buildSharingConfiguration() } else { configuration = buildConfiguration(param1) clientConfiguration = buildClientConfiguration(args[0]) biometryConfiguration = buildBiometryConfiguration(args[1]) keychainConfiguration = buildKeychainConfiguration(args[2]) + sharingConfiguration = buildSharingConfiguration(args[3]) } configRegister.set(this.instanceId, { configuration: configuration, clientConfiguration: clientConfiguration, biometryConfiguration: biometryConfiguration, - keychainConfiguration: keychainConfiguration + keychainConfiguration: keychainConfiguration, + sharingConfiguration: sharingConfiguration }) - return NativeWrapper.thisCallBool("configure", this.instanceId, configuration, clientConfiguration, biometryConfiguration, keychainConfiguration) + return NativeWrapper.thisCallBool("configure", this.instanceId, configuration, clientConfiguration, biometryConfiguration, keychainConfiguration, sharingConfiguration) } /** @@ -186,6 +200,14 @@ export class PowerAuth { return NativeWrapper.thisCallBool("hasPendingActivation", this.instanceId); } + /** + * Check if there's an external pending operation started in another application. + * @returns A promise with information about external pending operation. + */ + getExternalPendingOperation(): Promise { + return NativeWrapper.thisCall("getExternalPendingOperation", this.instanceId); + } + /** * Fetch the activation status for current activation. * @@ -570,6 +592,7 @@ interface InstanceConfigurations { clientConfiguration: PowerAuthClientConfigurationType biometryConfiguration: PowerAuthBiometryConfigurationType keychainConfiguration: PowerAuthKeychainConfigurationType + sharingConfiguration: PowerAuthSharingConfigurationType } /** diff --git a/src/index.ts b/src/index.ts index f5e7c06..f19bc7f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -37,6 +37,8 @@ export * from './model/PowerAuthConfirmRecoveryCodeDataResult'; export * from './model/PowerAuthCreateActivationResult'; export * from './model/PowerAuthError'; export * from './model/PowerAuthKeychainConfiguration'; +export * from './model/PowerAuthSharingConfiguration'; +export * from './model/PowerAuthExternalPendingOperation'; export * from './model/PowerAuthRecoveryActivationData'; export * from './model/PowerAuthPassword'; export * from './model/PowerAuthEncryptor'; diff --git a/src/model/PowerAuthError.ts b/src/model/PowerAuthError.ts index 88ab1b4..ca1f99a 100644 --- a/src/model/PowerAuthError.ts +++ b/src/model/PowerAuthError.ts @@ -183,6 +183,15 @@ export enum PowerAuthErrorCode { /** Biometric authentication failed */ BIOMETRY_FAILED = "BIOMETRY_FAILED", + /** + * ### iOS Specific + * + * The requested function is not available due to an external application is doing the sensitive operation + * at the same time. The recommended action is to instruct the user to switch to the application that started + * the sensitive operation. You can investigate the type of operation by calling `PowerAuth.getExternalPendingOperation()`. + */ + EXTERNAL_PENDING_OPERATION = "EXTERNAL_PENDING_OPERATION", + /** * When password is not set during activation commit. * @deprecated "WRONG_PARAM" is returned in this case. diff --git a/src/model/PowerAuthExternalPendingOperation.ts b/src/model/PowerAuthExternalPendingOperation.ts new file mode 100644 index 0000000..dcd448a --- /dev/null +++ b/src/model/PowerAuthExternalPendingOperation.ts @@ -0,0 +1,41 @@ +/* + * Copyright 2024 Wultra s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * ### iOS specific + * + * The `PowerAuthExternalPendingOperationType` defines types of operation + * started in another application that share activation data. + */ +export type PowerAuthExternalPendingOperationType = "ACTIVATION" | "PROTOCOL_UPGRADE" + +/** + * ### iOS specific + * + * The `PowerAuthExternalPendingOperation` interface contains data that can identify an external + * application that started the critical operation. + */ +export interface PowerAuthExternalPendingOperation { + /** + * Type of operation running in another application. + */ + externalOperationType: PowerAuthExternalPendingOperationType + /** + * Identifier of external application that started the operation. This is the same identifier + * you provided to `PowerAuthSharingConfiguration` during the PowerAuth initialization. + */ + externalApplicationId: string +} \ No newline at end of file diff --git a/src/model/PowerAuthSharingConfiguration.ts b/src/model/PowerAuthSharingConfiguration.ts new file mode 100644 index 0000000..2c4c644 --- /dev/null +++ b/src/model/PowerAuthSharingConfiguration.ts @@ -0,0 +1,104 @@ +/* + * Copyright 2024 Wultra s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Interface that representing the activation data sharing settings. + */ +export interface PowerAuthSharingConfigurationType { + /** + * ### iOS specific + * + * Name of app group that allows you sharing data between multiple applications. Be aware that the value + * overrides `accessGroupName` property if it's provided in `PowerAuthKeychainConfiguration`. + * + * The UTF-8 representation of this string should not exceed 26 bytes, due to internal limitations applied + * on the operating system level. + */ + readonly appGroup: string + /** + * ### iOS specific + * + * Unique application identifier. This identifier helps you to determine which application + * currently holds the lock on activation data in a special operations. + * + * The length of identifier cannot exceed 127 bytes if represented as UTF-8 string. It's recommended + * to use application's main bundle identifier, but in general, it's up to you how you identify your + * own applications. + */ + readonly appIdentifier: string + /** + * ### iOS specific + * + * Keychain access group name used by the PowerAuthSDK keychain instances. + */ + readonly keychainAccessGroup: string + + /** + * ### iOS specific + * + * Optional identifier of memory shared between the applications in app group. If identifier is not provided + * then PowerAuthSDK calculate unique identifier based on `PowerAuth.instanceId`. + * + * You can set this property in case that PowerAuth SDK generates identifier that collide with your application's + * functionality. The configuration of PowerAuthSDK instance always contains an actual identifier used for its + * shared memory initialization, so you can test whether the generated identifier is OK. + * + * The length of identifier cannot exceed 4 bytes if represented as UTF8 string. This is an operating system + * limitation. + */ + readonly sharedMemoryIdentifier?: string +} + +/** + * ### iOS specific + * + * The `PowerAuthSharingConfiguration` class contains configuration required for PowerAuthSDK instances shared + * accross multiple applications or application and its native extensions. + */ +export class PowerAuthSharingConfiguration implements PowerAuthSharingConfigurationType { + appGroup: string + appIdentifier: string + keychainAccessGroup: string + sharedMemoryIdentifier?: string + /** + * Construct configuration with required parameters + * @param appGroup Name of app group that allows you sharing data between multiple applications. + * @param appIdentifier Unique application identifier. This identifier helps you to determine which application currently holds the lock on activation data in a special operations. + * @param keychainAccessGroup Keychain access group name used by the PowerAuthSDK keychain instances. + * @param sharedMemoryIdentifier Optional identifier of memory shared between the applications in app group. + */ + constructor(appGroup: string, appIdentifier: string, keychainAccessGroup: string, sharedMemoryIdentifier: string | undefined = undefined) { + this.appGroup = appGroup + this.appIdentifier = appIdentifier + this.keychainAccessGroup = keychainAccessGroup + this.sharedMemoryIdentifier = sharedMemoryIdentifier + } +} + +/** + * Function create a frozen object implementing `PowerAuthSharingConfigurationType` with all required properties set. + * @param input Optional application's configuration. If not provided, then the default values are set. + * @returns Frozen object implementing `PowerAuthSharingConfigurationType` with additional property indicating that configuration is provided. + */ +export function buildSharingConfiguration(input: PowerAuthSharingConfigurationType | undefined = undefined): PowerAuthSharingConfigurationType { + return Object.freeze({ + appGroup: input?.appGroup ?? "", + appIdentifier: input?.appIdentifier ?? "", + keychainAccessGroup: input?.keychainAccessGroup ?? "", + sharedMemoryIdentifier: input?.sharedMemoryIdentifier, + isProvided: input !== undefined + }) +} \ No newline at end of file diff --git a/testapp/_tests/PowerAuth_Activation.test.ts b/testapp/_tests/PowerAuth_Activation.test.ts index 0780b0f..9e45b31 100644 --- a/testapp/_tests/PowerAuth_Activation.test.ts +++ b/testapp/_tests/PowerAuth_Activation.test.ts @@ -41,6 +41,7 @@ export class PowerAuth_ActivationTests extends TestWithActivation { expect(await sdk.hasValidActivation()).toBe(false) expect(await sdk.getActivationIdentifier()).toBeUndefined() expect(await sdk.getActivationFingerprint()).toBeUndefined() + expect(await sdk.getExternalPendingOperation()).toBeUndefined() await this.runFailingMethodsDuringActivation('BEGIN', PowerAuthErrorCode.MISSING_ACTIVATION, PowerAuthErrorCode.MISSING_ACTIVATION) await expect(async () => await sdk.commitActivation(this.credentials.invalidKnowledge)).toThrow({errorCode: PowerAuthErrorCode.INVALID_ACTIVATION_STATE}) diff --git a/testapp/_tests/PowerAuth_Configure.test.ts b/testapp/_tests/PowerAuth_Configure.test.ts index 09890c7..b2a13e9 100644 --- a/testapp/_tests/PowerAuth_Configure.test.ts +++ b/testapp/_tests/PowerAuth_Configure.test.ts @@ -60,6 +60,8 @@ export class PowerAuth_ConfigureTests extends TestWithServer { expect(sdk2.clientConfiguration).toBeDefined() expect(sdk1.biometryConfiguration).toBeDefined() expect(sdk2.biometryConfiguration).toBeDefined() + expect(sdk1.sharingConfiguration).toBeDefined() + expect(sdk2.sharingConfiguration).toBeDefined() // pa1 & pa2 should be configured now, because PowerAuth is just a thin envelope // keeping only essential values @@ -74,6 +76,8 @@ export class PowerAuth_ConfigureTests extends TestWithServer { expect(pa2.clientConfiguration).toBeDefined() expect(pa1.biometryConfiguration).toBeDefined() expect(pa2.biometryConfiguration).toBeDefined() + expect(pa1.sharingConfiguration).toBeDefined() + expect(pa2.sharingConfiguration).toBeDefined() } async testReconfigureWhileActive() { @@ -93,6 +97,8 @@ export class PowerAuth_ConfigureTests extends TestWithServer { const keychainConfig2 = sdk2.keychainConfiguration const biometryConfig1 = sdk1.biometryConfiguration const biometryConfig2 = sdk2.biometryConfiguration + const sharingConfig1 = sdk1.sharingConfiguration + const sharingConfig2 = sdk2.sharingConfiguration expect(config1).toBeDefined() expect(config2).toBeDefined() @@ -102,6 +108,8 @@ export class PowerAuth_ConfigureTests extends TestWithServer { expect(keychainConfig2).toBeDefined() expect(biometryConfig1).toBeDefined() expect(biometryConfig2).toBeDefined() + expect(sharingConfig1).toBeDefined() + expect(sharingConfig2).toBeDefined() await helper1.createActivation(undefined, this.prepareData(this.instance1)) await helper2.createActivation(undefined, this.prepareData(this.instance2)) @@ -133,6 +141,16 @@ export class PowerAuth_ConfigureTests extends TestWithServer { expect(await sdk2.validatePassword(this.password2)).toSucceed() } + async iosTestActivationSharing() { + const helper1 = await this.getHelper1() + const sdk1 = helper1.powerAuthSdk + expect(await sdk1.isConfigured()).toBe(true) + expect(sdk1.sharingConfiguration?.appGroup).toBe("group.com.wultra.testGroup") + expect(sdk1.sharingConfiguration?.appIdentifier).toBe("SharedInstanceTests") + expect(sdk1.sharingConfiguration?.keychainAccessGroup).toBe("fake.accessGroup") + expect(sdk1.sharingConfiguration?.sharedMemoryIdentifier).toBe("tst1") + } + async runMethodsThatMustFail(sdk: PowerAuth) { const commitAuth = PowerAuthAuthentication.commitWithPassword('1234') const signAuth = PowerAuthAuthentication.possession() @@ -216,6 +234,19 @@ export class PowerAuth_ConfigureTests extends TestWithServer { customizePowerAuthActivation(activation: PowerAuthActivation) {} prepareData(instanceId: string): CustomActivationHelperPrepareData { + if (this.currentTestName === 'iosTestActivationSharing') { + return { + powerAuthInstanceId: instanceId, + useConfigObjects: true, + password: instanceId === this.instance1 ? this.password1 : this.password2, + sharingConfiguration: { + appGroup: "group.com.wultra.testGroup", + appIdentifier: "SharedInstanceTests", + keychainAccessGroup: "fake.accessGroup", // This will work only in simulator + sharedMemoryIdentifier: "tst1" + } + } + } return { powerAuthInstanceId: instanceId, useConfigObjects: true, diff --git a/testapp/_tests/helpers/RNActivationHelper.ts b/testapp/_tests/helpers/RNActivationHelper.ts index 49dd73d..c7a75bf 100644 --- a/testapp/_tests/helpers/RNActivationHelper.ts +++ b/testapp/_tests/helpers/RNActivationHelper.ts @@ -24,7 +24,8 @@ import { PowerAuthClientConfiguration, PowerAuthConfiguration, PowerAuthCreateActivationResult, - PowerAuthKeychainConfiguration } from "react-native-powerauth-mobile-sdk"; + PowerAuthKeychainConfiguration, + PowerAuthSharingConfiguration } from "react-native-powerauth-mobile-sdk"; import { RNHttpClient } from './RNHttpClient' /** @@ -92,6 +93,11 @@ export interface CustomActivationHelperPrepareData extends ActivationHelperPrepa * Note that the configuration will be ignored if `useConfigObjects` is false and `instanceConfig` is undefined. */ biometryConfig?: PowerAuthBiometryConfiguration + /** + * If provided, then this sharing configuration will be applied to PowerAuth instance. + * Note that the configuration will be ignored if `useConfigObjects` is false and `instanceConfig` is undefined. + */ + sharingConfiguration?: PowerAuthSharingConfiguration } /** @@ -121,7 +127,7 @@ export async function createActivationHelper(server: PowerAuthTestServer, cfg: T clientConfig = new PowerAuthClientConfiguration() clientConfig.enableUnsecureTraffic = allowUnsecure } - await sdk.configure(instanceConfig, clientConfig, pd.biometryConfig, pd.keychainConfig) + await sdk.configure(instanceConfig, clientConfig, pd.biometryConfig, pd.keychainConfig, pd.sharingConfiguration) } else { // Use legacy configuration await sdk.configure(appSetup.appKey, appSetup.appSecret, appSetup.masterServerPublicKey, cfg.enrollment.baseUrl, allowUnsecure) diff --git a/testapp/ios/testapp.xcodeproj/project.pbxproj b/testapp/ios/testapp.xcodeproj/project.pbxproj index 360dde2..fa6310d 100644 --- a/testapp/ios/testapp.xcodeproj/project.pbxproj +++ b/testapp/ios/testapp.xcodeproj/project.pbxproj @@ -39,6 +39,7 @@ 5709B34CF0A7D63546082F79 /* Pods-testapp.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-testapp.release.xcconfig"; path = "Target Support Files/Pods-testapp/Pods-testapp.release.xcconfig"; sourceTree = ""; }; 5DCACB8F33CDC322A6C60F78 /* libPods-testapp.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-testapp.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = testapp/LaunchScreen.storyboard; sourceTree = ""; }; + BF9FCA252C8F1BC100F6E1BE /* testapp.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; name = testapp.entitlements; path = testapp/testapp.entitlements; sourceTree = ""; }; ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; }; /* End PBXFileReference section */ @@ -81,6 +82,7 @@ 13B07FAE1A68108700A75B9A /* testapp */ = { isa = PBXGroup; children = ( + BF9FCA252C8F1BC100F6E1BE /* testapp.entitlements */, 13B07FAF1A68108700A75B9A /* AppDelegate.h */, 13B07FB01A68108700A75B9A /* AppDelegate.mm */, 13B07FB51A68108700A75B9A /* Images.xcassets */, @@ -414,6 +416,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = testapp/testapp.entitlements; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = KTT9G859MR; ENABLE_BITCODE = NO; @@ -442,6 +445,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = testapp/testapp.entitlements; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = KTT9G859MR; INFOPLIST_FILE = testapp/Info.plist; diff --git a/testapp/ios/testapp/testapp.entitlements b/testapp/ios/testapp/testapp.entitlements new file mode 100644 index 0000000..f3d4a4f --- /dev/null +++ b/testapp/ios/testapp/testapp.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.application-groups + + group.com.wultra.testGroup + + + From f6842193783f87098508e3d526f8af4a9ccdd0e7 Mon Sep 17 00:00:00 2001 From: Jan Kobersky Date: Tue, 10 Sep 2024 16:21:10 +0200 Subject: [PATCH 2/5] Post merge fixes --- ios/PowerAuth/PowerAuthModule.m | 7 +- .../cordova/ios/PowerAuth/RCTConvert.h | 35 --- .../cordova/ios/PowerAuth/RCTConvert.mm | 288 ------------------ package-lock.json | 4 +- src/PowerAuth.ts | 2 +- testapp/ios/Podfile.lock | 6 +- testapp/package-lock.json | 13 +- testapp/package.json | 4 +- testapp/src/TestExecutor.ts | 28 +- 9 files changed, 39 insertions(+), 348 deletions(-) diff --git a/ios/PowerAuth/PowerAuthModule.m b/ios/PowerAuth/PowerAuthModule.m index 82260f6..bcafceb 100644 --- a/ios/PowerAuth/PowerAuthModule.m +++ b/ios/PowerAuth/PowerAuthModule.m @@ -191,10 +191,8 @@ + (BOOL) requiresMainQueueSetup } PAJS_METHOD_END -RCT_REMAP_METHOD(getExternalPendingOperation, - instanceId:(NSString*)instanceId - getExternalPendingOperationResolve:(RCTPromiseResolveBlock)resolve - getExternalPendingOperationReject:(RCTPromiseRejectBlock)reject) +PAJS_METHOD_START(getExternalPendingOperation, + PAJS_ARGUMENT(instanceId, NSString*)) { PA_BLOCK_START PowerAuthExternalPendingOperation * pendingOperation = powerAuth.externalPendingOperation; @@ -208,6 +206,7 @@ + (BOOL) requiresMainQueueSetup } PA_BLOCK_END } +PAJS_METHOD_END PAJS_METHOD_START(fetchActivationStatus, PAJS_ARGUMENT(instanceId, NSString*)) diff --git a/other-platforms-support/cordova/ios/PowerAuth/RCTConvert.h b/other-platforms-support/cordova/ios/PowerAuth/RCTConvert.h index 8c23b00..5b88551 100644 --- a/other-platforms-support/cordova/ios/PowerAuth/RCTConvert.h +++ b/other-platforms-support/cordova/ios/PowerAuth/RCTConvert.h @@ -59,47 +59,12 @@ RCT_EXTERN void RCTSetDefaultColorSpace(RCTColorSpace colorSpace); + (NSTimeZone *)NSTimeZone:(id)json; + (NSTimeInterval)NSTimeInterval:(id)json; -+ (NSLineBreakMode)NSLineBreakMode:(id)json; -+ (NSTextAlignment)NSTextAlignment:(id)json; -+ (NSUnderlineStyle)NSUnderlineStyle:(id)json; -+ (NSWritingDirection)NSWritingDirection:(id)json; -+ (NSLineBreakStrategy)NSLineBreakStrategy:(id)json; -+ (UITextAutocapitalizationType)UITextAutocapitalizationType:(id)json; -+ (UITextFieldViewMode)UITextFieldViewMode:(id)json; -+ (UIKeyboardType)UIKeyboardType:(id)json; -+ (UIKeyboardAppearance)UIKeyboardAppearance:(id)json; -+ (UIReturnKeyType)UIReturnKeyType:(id)json; -+ (UIUserInterfaceStyle)UIUserInterfaceStyle:(id)json API_AVAILABLE(ios(12)); -+ (UIInterfaceOrientationMask)UIInterfaceOrientationMask:(NSString *)orientation; -+ (UIModalPresentationStyle)UIModalPresentationStyle:(id)json; - -#if !TARGET_OS_TV -+ (UIDataDetectorTypes)UIDataDetectorTypes:(id)json; -#endif - -+ (UIViewContentMode)UIViewContentMode:(id)json; - -+ (CGFloat)CGFloat:(id)json; -+ (CGPoint)CGPoint:(id)json; -+ (CGSize)CGSize:(id)json; -+ (CGRect)CGRect:(id)json; -+ (UIEdgeInsets)UIEdgeInsets:(id)json; - -+ (CGLineCap)CGLineCap:(id)json; -+ (CGLineJoin)CGLineJoin:(id)json; - -+ (CGAffineTransform)CGAffineTransform:(id)json; - + (NSArray *)NSArrayArray:(id)json; + (NSArray *)NSStringArray:(id)json; + (NSArray *> *)NSStringArrayArray:(id)json; + (NSArray *)NSDictionaryArray:(id)json; -+ (NSArray *)NSURLArray:(id)json; + (NSArray *)NSNumberArray:(id)json; -typedef NSArray CGColorArray; -+ (CGColorArray *)CGColorArray:(id)json; - /** * Convert a JSON object to a Plist-safe equivalent by stripping null values. */ diff --git a/other-platforms-support/cordova/ios/PowerAuth/RCTConvert.mm b/other-platforms-support/cordova/ios/PowerAuth/RCTConvert.mm index 51e9edd..a650278 100644 --- a/other-platforms-support/cordova/ios/PowerAuth/RCTConvert.mm +++ b/other-platforms-support/cordova/ios/PowerAuth/RCTConvert.mm @@ -75,139 +75,6 @@ + (NSIndexSet *)NSIndexSet:(id)json return indexSet; } -//+ (NSURL *)NSURL:(id)json -//{ -// NSString *path = [self NSString:RCTNilIfNull(json)]; -// if (!path) { -// return nil; -// } -// -// @try { // NSURL has a history of crashing with bad input, so let's be safe -// NSURL *URL = [NSURL URLWithString:path]; -// if (URL.scheme) { // Was a well-formed absolute URL -// return URL; -// } -// -// // Check if it has a scheme -// if ([path rangeOfString:@"://"].location != NSNotFound) { -// NSMutableCharacterSet *urlAllowedCharacterSet = [NSMutableCharacterSet new]; -// [urlAllowedCharacterSet formUnionWithCharacterSet:[NSCharacterSet URLUserAllowedCharacterSet]]; -// [urlAllowedCharacterSet formUnionWithCharacterSet:[NSCharacterSet URLPasswordAllowedCharacterSet]]; -// [urlAllowedCharacterSet formUnionWithCharacterSet:[NSCharacterSet URLHostAllowedCharacterSet]]; -// [urlAllowedCharacterSet formUnionWithCharacterSet:[NSCharacterSet URLPathAllowedCharacterSet]]; -// [urlAllowedCharacterSet formUnionWithCharacterSet:[NSCharacterSet URLQueryAllowedCharacterSet]]; -// [urlAllowedCharacterSet formUnionWithCharacterSet:[NSCharacterSet URLFragmentAllowedCharacterSet]]; -// path = [path stringByAddingPercentEncodingWithAllowedCharacters:urlAllowedCharacterSet]; -// URL = [NSURL URLWithString:path]; -// if (URL) { -// return URL; -// } -// } -// -// // Assume that it's a local path -// path = path.stringByRemovingPercentEncoding; -// if ([path hasPrefix:@"~"]) { -// // Path is inside user directory -// path = path.stringByExpandingTildeInPath; -// } else if (!path.absolutePath) { -// // Assume it's a resource path -// path = [[NSBundle mainBundle].resourcePath stringByAppendingPathComponent:path]; -// } -// if (!(URL = [NSURL fileURLWithPath:path])) { -// RCTLogConvertError(json, @"a valid URL"); -// } -// return URL; -// } @catch (__unused NSException *e) { -// RCTLogConvertError(json, @"a valid URL"); -// return nil; -// } -//} -// -//RCT_ENUM_CONVERTER( -// NSURLRequestCachePolicy, -// (@{ -// @"default" : @(NSURLRequestUseProtocolCachePolicy), -// @"reload" : @(NSURLRequestReloadIgnoringLocalCacheData), -// @"force-cache" : @(NSURLRequestReturnCacheDataElseLoad), -// @"only-if-cached" : @(NSURLRequestReturnCacheDataDontLoad), -// }), -// NSURLRequestUseProtocolCachePolicy, -// integerValue) -// -//+ (NSURLRequest *)NSURLRequest:(id)json -//{ -// if ([json isKindOfClass:[NSString class]]) { -// NSURL *URL = [self NSURL:json]; -// return URL ? [NSURLRequest requestWithURL:URL] : nil; -// } -// if ([json isKindOfClass:[NSDictionary class]]) { -// NSString *URLString = json[@"uri"] ?: json[@"url"]; -// -// NSURL *URL; -// NSString *bundleName = json[@"bundle"]; -// if (bundleName) { -// URLString = [NSString stringWithFormat:@"%@.bundle/%@", bundleName, URLString]; -// } -// -// URL = [self NSURL:URLString]; -// if (!URL) { -// return nil; -// } -// -// NSData *body = [self NSData:json[@"body"]]; -// NSString *method = [self NSString:json[@"method"]].uppercaseString ?: @"GET"; -// NSURLRequestCachePolicy cachePolicy = [self NSURLRequestCachePolicy:json[@"cache"]]; -// NSDictionary *headers = [self NSDictionary:json[@"headers"]]; -// if ([method isEqualToString:@"GET"] && headers == nil && body == nil && -// cachePolicy == NSURLRequestUseProtocolCachePolicy) { -// return [NSURLRequest requestWithURL:URL]; -// } -// -// if (headers) { -// __block BOOL allHeadersAreStrings = YES; -// [headers enumerateKeysAndObjectsUsingBlock:^(NSString *key, id header, BOOL *stop) { -// if (![header isKindOfClass:[NSString class]]) { -// RCTLogInfo( -// @"Values of HTTP headers passed must be of type string. " -// "Value of header '%@' is not a string.", -// key); -// allHeadersAreStrings = NO; -// *stop = YES; -// } -// }]; -// if (!allHeadersAreStrings) { -// // Set headers to nil here to avoid crashing later. -// headers = nil; -// } -// } -// -// NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:URL]; -// request.HTTPBody = body; -// request.HTTPMethod = method; -// request.cachePolicy = cachePolicy; -// request.allHTTPHeaderFields = headers; -// return [request copy]; -// } -// if (json) { -// RCTLogConvertError(json, @"a valid URLRequest"); -// } -// return nil; -//} - -//+ (RCTFileURL *)RCTFileURL:(id)json -//{ -// NSURL *fileURL = [self NSURL:json]; -// if (!fileURL.fileURL) { -// RCTLogInfo(@"URI must be a local file, '%@' isn't.", fileURL); -// return nil; -// } -// if (![[NSFileManager defaultManager] fileExistsAtPath:fileURL.path]) { -// RCTLogInfo(@"File '%@' could not be found.", fileURL); -// return nil; -// } -// return fileURL; -//} - + (NSDate *)NSDate:(id)json { if ([json isKindOfClass:[NSNumber class]]) { @@ -362,158 +229,6 @@ + (NSLineBreakStrategy)NSLineBreakStrategy:(id)json RCT_DYNAMIC } } -RCT_ENUM_CONVERTER( - UITextAutocapitalizationType, - (@{ - @"none" : @(UITextAutocapitalizationTypeNone), - @"words" : @(UITextAutocapitalizationTypeWords), - @"sentences" : @(UITextAutocapitalizationTypeSentences), - @"characters" : @(UITextAutocapitalizationTypeAllCharacters) - }), - UITextAutocapitalizationTypeSentences, - integerValue) - -RCT_ENUM_CONVERTER( - UITextFieldViewMode, - (@{ - @"never" : @(UITextFieldViewModeNever), - @"while-editing" : @(UITextFieldViewModeWhileEditing), - @"unless-editing" : @(UITextFieldViewModeUnlessEditing), - @"always" : @(UITextFieldViewModeAlways), - }), - UITextFieldViewModeNever, - integerValue) - -+ (UIKeyboardType)UIKeyboardType:(id)json RCT_DYNAMIC -{ - static NSDictionary *mapping; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - NSMutableDictionary *temporaryMapping = [NSMutableDictionary dictionaryWithDictionary:@{ - @"default" : @(UIKeyboardTypeDefault), - @"ascii-capable" : @(UIKeyboardTypeASCIICapable), - @"numbers-and-punctuation" : @(UIKeyboardTypeNumbersAndPunctuation), - @"url" : @(UIKeyboardTypeURL), - @"number-pad" : @(UIKeyboardTypeNumberPad), - @"phone-pad" : @(UIKeyboardTypePhonePad), - @"name-phone-pad" : @(UIKeyboardTypeNamePhonePad), - @"email-address" : @(UIKeyboardTypeEmailAddress), - @"decimal-pad" : @(UIKeyboardTypeDecimalPad), - @"twitter" : @(UIKeyboardTypeTwitter), - @"web-search" : @(UIKeyboardTypeWebSearch), - // Added for Android compatibility - @"numeric" : @(UIKeyboardTypeDecimalPad), - }]; - temporaryMapping[@"ascii-capable-number-pad"] = @(UIKeyboardTypeASCIICapableNumberPad); - mapping = temporaryMapping; - }); - - UIKeyboardType type = - (UIKeyboardType)RCTConvertEnumValue("UIKeyboardType", mapping, @(UIKeyboardTypeDefault), json).integerValue; - return type; -} - -RCT_MULTI_ENUM_CONVERTER( - UIDataDetectorTypes, - (@{ - @"phoneNumber" : @(UIDataDetectorTypePhoneNumber), - @"link" : @(UIDataDetectorTypeLink), - @"address" : @(UIDataDetectorTypeAddress), - @"calendarEvent" : @(UIDataDetectorTypeCalendarEvent), - @"trackingNumber" : @(UIDataDetectorTypeShipmentTrackingNumber), - @"flightNumber" : @(UIDataDetectorTypeFlightNumber), - @"lookupSuggestion" : @(UIDataDetectorTypeLookupSuggestion), - @"none" : @(UIDataDetectorTypeNone), - @"all" : @(UIDataDetectorTypeAll), - }), - UIDataDetectorTypePhoneNumber, - unsignedLongLongValue) - -RCT_ENUM_CONVERTER( - UIKeyboardAppearance, - (@{ - @"default" : @(UIKeyboardAppearanceDefault), - @"light" : @(UIKeyboardAppearanceLight), - @"dark" : @(UIKeyboardAppearanceDark), - }), - UIKeyboardAppearanceDefault, - integerValue) - -RCT_ENUM_CONVERTER( - UIReturnKeyType, - (@{ - @"default" : @(UIReturnKeyDefault), - @"go" : @(UIReturnKeyGo), - @"google" : @(UIReturnKeyGoogle), - @"join" : @(UIReturnKeyJoin), - @"next" : @(UIReturnKeyNext), - @"route" : @(UIReturnKeyRoute), - @"search" : @(UIReturnKeySearch), - @"send" : @(UIReturnKeySend), - @"yahoo" : @(UIReturnKeyYahoo), - @"done" : @(UIReturnKeyDone), - @"emergency-call" : @(UIReturnKeyEmergencyCall), - }), - UIReturnKeyDefault, - integerValue) - -RCT_ENUM_CONVERTER( - UIUserInterfaceStyle, - (@{ - @"unspecified" : @(UIUserInterfaceStyleUnspecified), - @"light" : @(UIUserInterfaceStyleLight), - @"dark" : @(UIUserInterfaceStyleDark), - }), - UIUserInterfaceStyleUnspecified, - integerValue) - -RCT_ENUM_CONVERTER( - UIInterfaceOrientationMask, - (@{ - @"ALL" : @(UIInterfaceOrientationMaskAll), - @"PORTRAIT" : @(UIInterfaceOrientationMaskPortrait), - @"LANDSCAPE" : @(UIInterfaceOrientationMaskLandscape), - @"LANDSCAPE_LEFT" : @(UIInterfaceOrientationMaskLandscapeLeft), - @"LANDSCAPE_RIGHT" : @(UIInterfaceOrientationMaskLandscapeRight), - }), - NSNotFound, - unsignedIntegerValue) - -RCT_ENUM_CONVERTER( - UIModalPresentationStyle, - (@{ - @"fullScreen" : @(UIModalPresentationFullScreen), - @"pageSheet" : @(UIModalPresentationPageSheet), - @"formSheet" : @(UIModalPresentationFormSheet), - @"overFullScreen" : @(UIModalPresentationOverFullScreen), - }), - UIModalPresentationFullScreen, - integerValue) - -RCT_ENUM_CONVERTER( - UIViewContentMode, - (@{ - @"scale-to-fill" : @(UIViewContentModeScaleToFill), - @"scale-aspect-fit" : @(UIViewContentModeScaleAspectFit), - @"scale-aspect-fill" : @(UIViewContentModeScaleAspectFill), - @"redraw" : @(UIViewContentModeRedraw), - @"center" : @(UIViewContentModeCenter), - @"top" : @(UIViewContentModeTop), - @"bottom" : @(UIViewContentModeBottom), - @"left" : @(UIViewContentModeLeft), - @"right" : @(UIViewContentModeRight), - @"top-left" : @(UIViewContentModeTopLeft), - @"top-right" : @(UIViewContentModeTopRight), - @"bottom-left" : @(UIViewContentModeBottomLeft), - @"bottom-right" : @(UIViewContentModeBottomRight), - // Cross-platform values - @"cover" : @(UIViewContentModeScaleAspectFill), - @"contain" : @(UIViewContentModeScaleAspectFit), - @"stretch" : @(UIViewContentModeScaleToFill), - }), - UIViewContentModeScaleAspectFill, - integerValue) - /** * This macro is used for creating converter functions for structs that consist * of a number of CGFloat properties, such as CGPoint, CGRect, etc. @@ -563,9 +278,6 @@ +(type)type : (id)json \ return values; } -RCT_ARRAY_CONVERTER(NSURL) -RCT_ARRAY_CONVERTER(UIColor) - /** * This macro is used for creating converter functions for directly * representable json array values that require no conversion. diff --git a/package-lock.json b/package-lock.json index c873850..449bc39 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "react-native-powerauth-mobile-sdk", - "version": "2.5.0", + "version": "2.5.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "react-native-powerauth-mobile-sdk", - "version": "2.5.0", + "version": "2.5.2", "license": "Apache 2.0", "dependencies": { "node-fetch": ">=2.6.1" diff --git a/src/PowerAuth.ts b/src/PowerAuth.ts index c6b6622..0ef7a5d 100644 --- a/src/PowerAuth.ts +++ b/src/PowerAuth.ts @@ -205,7 +205,7 @@ export class PowerAuth { * @returns A promise with information about external pending operation. */ getExternalPendingOperation(): Promise { - return NativeWrapper.thisCall("getExternalPendingOperation", this.instanceId); + return NativeWrapper.thisCallNull("getExternalPendingOperation", this.instanceId); } /** diff --git a/testapp/ios/Podfile.lock b/testapp/ios/Podfile.lock index ffa2157..ad5bc75 100644 --- a/testapp/ios/Podfile.lock +++ b/testapp/ios/Podfile.lock @@ -273,8 +273,8 @@ PODS: - react-native-config/App (= 1.5.0) - react-native-config/App (1.5.0): - React-Core - - react-native-powerauth-mobile-sdk (2.5.0): - - PowerAuth2 (~> 1.7.8) + - react-native-powerauth-mobile-sdk (2.5.2): + - PowerAuth2 (~> 1.7.9) - React-Core - React-perflogger (0.71.6) - React-RCTActionSheet (0.71.6): @@ -509,7 +509,7 @@ SPEC CHECKSUMS: React-jsinspector: d5ce2ef3eb8fd30c28389d0bc577918c70821bd6 React-logger: 9332c3e7b4ef007a0211c0a9868253aac3e1da82 react-native-config: 5330c8258265c1e5fdb8c009d2cabd6badd96727 - react-native-powerauth-mobile-sdk: 115eb6efa07267753b8e41796070f7b1a9a97072 + react-native-powerauth-mobile-sdk: 6ed31fca4b6ba917b65faedd125421522d2de1fe React-perflogger: 43392072a5b867a504e2b4857606f8fc5a403d7f React-RCTActionSheet: c7b67c125bebeda9fb19fc7b200d85cb9d6899c4 React-RCTAnimation: c2de79906f607986633a7114bee44854e4c7e2f5 diff --git a/testapp/package-lock.json b/testapp/package-lock.json index e957980..182ccdd 100644 --- a/testapp/package-lock.json +++ b/testapp/package-lock.json @@ -13,7 +13,7 @@ "react": "18.2.0", "react-native": "0.71.6", "react-native-config": "^1.5.0", - "react-native-powerauth-mobile-sdk": "file:../build/react-native/react-native-powerauth-mobile-sdk-2.5.0.tgz" + "react-native-powerauth-mobile-sdk": "file:../build/react-native/react-native-powerauth-mobile-sdk-2.5.2.tgz" }, "devDependencies": { "@babel/core": "^7.20.0", @@ -32,7 +32,6 @@ } }, "..": { - "name": "react-native-powerauth-mobile-sdk", "version": "2.5.0", "extraneous": true, "license": "Apache 2.0", @@ -8962,9 +8961,9 @@ "integrity": "sha512-7F6bD7B8Xsn3JllxcwHhFcsl9aHIig47+3eN4IHFNqfLhZr++3ElDrcqfMzugM+niWbaMi7bJ0kAkAL8eCpdWg==" }, "node_modules/react-native-powerauth-mobile-sdk": { - "version": "2.5.0", - "resolved": "file:../build/react-native/react-native-powerauth-mobile-sdk-2.5.0.tgz", - "integrity": "sha512-vvFbXx8TUN3CaqgLt4J7jPsQqSDv3WrrCGOXPi/KGxi9b8AFyDLHYR1xpbMDvxBGhVu+sdczYm4twdszPp8U0g==", + "version": "2.5.2", + "resolved": "file:../build/react-native/react-native-powerauth-mobile-sdk-2.5.2.tgz", + "integrity": "sha512-T+fwihoqY2HbX2kjtg9uPmLlSdmi1D0hjDtHG/6iLp/DIPfA+r96jk4ZqD1NcwsIvBGFZU8xweUWJxfH2OMleg==", "license": "Apache 2.0", "dependencies": { "node-fetch": ">=2.6.1" @@ -17412,8 +17411,8 @@ "integrity": "sha512-7F6bD7B8Xsn3JllxcwHhFcsl9aHIig47+3eN4IHFNqfLhZr++3ElDrcqfMzugM+niWbaMi7bJ0kAkAL8eCpdWg==" }, "react-native-powerauth-mobile-sdk": { - "version": "file:../build/react-native/react-native-powerauth-mobile-sdk-2.5.0.tgz", - "integrity": "sha512-vvFbXx8TUN3CaqgLt4J7jPsQqSDv3WrrCGOXPi/KGxi9b8AFyDLHYR1xpbMDvxBGhVu+sdczYm4twdszPp8U0g==", + "version": "file:../build/react-native/react-native-powerauth-mobile-sdk-2.5.2.tgz", + "integrity": "sha512-T+fwihoqY2HbX2kjtg9uPmLlSdmi1D0hjDtHG/6iLp/DIPfA+r96jk4ZqD1NcwsIvBGFZU8xweUWJxfH2OMleg==", "requires": { "node-fetch": ">=2.6.1" } diff --git a/testapp/package.json b/testapp/package.json index 2c340f3..3b251eb 100644 --- a/testapp/package.json +++ b/testapp/package.json @@ -9,7 +9,7 @@ "pods": "pushd ios; pod install; popd", "test": "echo Just run this app; exit 1", "lint": "eslint .", - "reinstallPlugin": "(npm r react-native-powerauth-mobile-sdk || true) && npm i ../build/react-native/react-native-powerauth-mobile-sdk-2.5.0.tgz" + "reinstallPlugin": "(npm r react-native-powerauth-mobile-sdk || true) && npm i ../build/react-native/react-native-powerauth-mobile-sdk-2.5.2.tgz" }, "dependencies": { "chalk": "^4.1.0", @@ -17,7 +17,7 @@ "react": "18.2.0", "react-native": "0.71.6", "react-native-config": "^1.5.0", - "react-native-powerauth-mobile-sdk": "file:../build/react-native/react-native-powerauth-mobile-sdk-2.5.0.tgz" + "react-native-powerauth-mobile-sdk": "file:../build/react-native/react-native-powerauth-mobile-sdk-2.5.2.tgz" }, "devDependencies": { "@babel/core": "^7.20.0", diff --git a/testapp/src/TestExecutor.ts b/testapp/src/TestExecutor.ts index 0cd6eb1..5550d2d 100644 --- a/testapp/src/TestExecutor.ts +++ b/testapp/src/TestExecutor.ts @@ -23,12 +23,16 @@ import { TestRunner } from './testbed/TestRunner' export class TestServer { + private isRunning = true; // assume running + constructor() { // we want to re-route console outputs for easier "test infrastructure" and debugging on CI const logF = console.log; const warnF = console.warn; const errorF = console.error; const infoF = console.info; + + this.testRunning() console.log = (...params) => { this.log(params) @@ -52,18 +56,30 @@ export class TestServer { } log(data: any[]) { - this.call("log", data) + if (this.isRunning) { + this.call("log", data) + } } reportStatus(data: TestProgress) { - this.call("reportStatus", data) + if (this.isRunning) { + this.call("reportStatus", data) + } + } + + private async testRunning() { + try { + await this.call("test", {}); + this.isRunning = true + } catch (e) { + this.isRunning = false + console.log("Server not runnig") + } } - private call(method: string, object: any) { + private async call(method: string, object: any): Promise { // the server code is in the git root as "test-listener.js" - fetch("http://localhost:8083/" + method, { method: "POST", body: JSON.stringify(object) }).catch((e) => { - // do we need to react? - }) + return await fetch("http://localhost:8083/" + method, { method: "POST", body: JSON.stringify(object) }) } } From d037626dfa5021d846ce6ab076c95dd2edc8d712 Mon Sep 17 00:00:00 2001 From: Jan Kobersky Date: Wed, 11 Sep 2024 14:10:43 +0200 Subject: [PATCH 3/5] Fixed cordova tests --- gulpfile.js | 1 + test-listener.js | 2 +- testapp-cordova/gulpfile.js | 23 +++++++++++++++++++++++ testapp-cordova/src/App.tsx | 16 ++++++++++++++-- testapp-cordova/www/css/index.css | 28 ++++++++++++++++++++++------ testapp-cordova/www/index.html | 18 ++++++++++++------ 6 files changed, 73 insertions(+), 15 deletions(-) diff --git a/gulpfile.js b/gulpfile.js index bb2b931..ea74aba 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -151,6 +151,7 @@ const tmpDir = ".build"; "PowerAuthActivationState", "PowerAuthBiometryConfiguration", "PowerAuthBiometryStatus", + "PowerAuthBiometryType", "PowerAuthClientConfiguration", "PowerAuthConfiguration", "PowerAuthError", diff --git a/test-listener.js b/test-listener.js index 3d3df64..8b29e96 100644 --- a/test-listener.js +++ b/test-listener.js @@ -23,7 +23,7 @@ const http = require('http'); const { exit } = require('process'); const port = 8083 -const maxDurationSeconds = 180 // Test aer usually taking 60-90 seconds +const maxDurationSeconds = 240 // Test aer usually taking 60-90 seconds console.log(`Starting test listener @ localhost:${port}`) console.log(`If the tests won't finish in ${maxDurationSeconds} seconds, this script will exit with the error exit code.`) diff --git a/testapp-cordova/gulpfile.js b/testapp-cordova/gulpfile.js index 9ae95a8..c9a91b9 100644 --- a/testapp-cordova/gulpfile.js +++ b/testapp-cordova/gulpfile.js @@ -82,6 +82,28 @@ const patchNativeFiles = () => .src("patch-files/platforms/**/**", { base: "patch-files" }) .pipe(gulp.dest(".")) +const patchIOSPlists = () => { + + const plistPath = "platforms/ios/PowerAuthTest/PowerAuthTest-Info.plist"; + const entlPaths = ["platforms/ios/PowerAuthTest/Entitlements-Debug.plist", "platforms/ios/PowerAuthTest/Entitlements-Release.plist"] + const plistBuddy = "/usr/libexec/PlistBuddy" + const faceIdKey = "NSFaceIDUsageDescription" + const secGroupKey = "com.apple.security.application-groups" + const secGroupValue = "group.com.wultra.testGroup" + + return new Promise((resolve) => { + // we need to modify ios plist so we can test on faceid phones. The command check if the faceid key exist and if not, it will add it + exec(`${plistBuddy} -c "print :${faceIdKey}" ${plistPath} || ${plistBuddy} -c "add :${faceIdKey} string For Tests" ${plistPath}`) + + // we also need to add entitlements to ensure that the shared data tests will work + entlPaths.forEach((entlFile) => { + exec(`${plistBuddy} -c "print :${secGroupKey}:0" ${entlFile} || (${plistBuddy} -c "add :${secGroupKey} array" ${entlFile} && ${plistBuddy} -c "add :${secGroupKey}:0 string ${secGroupValue}" ${entlFile})`) + }) + + resolve() + }); +} + gulp.task("default", gulp.series( cleanTemp, copyTestFiles, @@ -90,4 +112,5 @@ gulp.task("default", gulp.series( cleanTemp, prepareIOS, patchNativeFiles, + patchIOSPlists, )); \ No newline at end of file diff --git a/testapp-cordova/src/App.tsx b/testapp-cordova/src/App.tsx index 4b1d7d1..3188e50 100644 --- a/testapp-cordova/src/App.tsx +++ b/testapp-cordova/src/App.tsx @@ -26,16 +26,28 @@ function onDeviceReady() { const statusEl = document.getElementById('tests-status'); const progressEl = document.getElementById('tests-progress'); + const messageEl = document.getElementById("test-message"); console.log('Running cordova-' + cordova.platformId + '@' + cordova.version); document.getElementById('deviceready').classList.add('ready'); const executor = new TestExecutor(async (_context, message, duration) => { - console.log(message) + messageEl.innerHTML = message; await new Promise(resolve => setTimeout(resolve, duration)) }, (progress) => { - progressEl.innerHTML = `${progress.succeeded} succeeded
${progress.failed} failed
${progress.skipped} skipped
out of total ${progress.total}`;; + progressEl.innerHTML = `${progress.succeeded} succeeded
${progress.failed} failed
${progress.skipped} skipped
out of total ${progress.total}`;; }, (finished) => { statusEl.innerHTML = finished ? "Tests running" : "Tests finished"; + messageEl.innerHTML = ""; + }) + + document.getElementById('tests-simple').addEventListener('click', (e) => { + executor.runTests(false) + }) + document.getElementById('tests-full').addEventListener('click', (e) => { + executor.runTests(true) + }) + document.getElementById('tests-stop').addEventListener('click', (e) => { + executor.cancelTests() }) } \ No newline at end of file diff --git a/testapp-cordova/www/css/index.css b/testapp-cordova/www/css/index.css index ecea7e8..ad7a26b 100644 --- a/testapp-cordova/www/css/index.css +++ b/testapp-cordova/www/css/index.css @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -* { + * { -webkit-tap-highlight-color: rgba(0,0,0,0); /* make transparent link selection, adjust last value opacity 0 to 1.0 */ } @@ -25,7 +25,7 @@ body { -webkit-text-size-adjust: none; /* prevent webkit from resizing text to fit */ -webkit-user-select: none; /* prevent copy paste, to allow, change 'none' to 'text' */ background-color:#E4E4E4; - background-image:linear-gradient(to bottom, #A7A7A7 0%, #E4E4E4 51%); + background-image:linear-gradient(to bottom, #A7A7A7 0%, #E4E4E4 51%) !important; font-family: system-ui, -apple-system, -apple-system-font, 'Segoe UI', 'Roboto', sans-serif; font-size:12px; height:100vh; @@ -47,7 +47,7 @@ body { width:225px; /* text area width */ text-align:center; padding:180px 0px 0px 0px; /* image height is 200px (bottom 20px are overlapped with text) */ - margin:-115px 0px 0px -112px; /* offset vertical: half of image height and text area height */ + margin:-200px 0px 0px -112px; /* offset vertical: half of image height and text area height */ /* offset horizontal: half of text area width */ } @@ -56,18 +56,19 @@ body { .app { background-position:left center; padding:75px 0px 75px 170px; /* padding-top + padding-bottom + text area = image height */ - margin:-90px 0px 0px -198px; /* offset vertical: half of image height */ + margin:-170px 0px 0px -198px; /* offset vertical: half of image height */ /* offset horizontal: half of image width and text area width */ } } h1 { - font-size:24px; - font-weight:normal; + font-size:22px; + font-weight:bold; margin:0px; overflow:visible; padding:0px; text-align:center; + color: #021b28; } .event { @@ -108,3 +109,18 @@ h1 { background-image:linear-gradient(to bottom, #585858 0%, #1B1B1B 51%); } } + +.button { + font-size: 15px; + padding: 8px !important; + margin-bottom: 4px; + margin-top: 2px; +} + +#test-message { + text-align: center; + padding: 20px 30px; + font-size: 16px; + font-weight: bold; + color: red; +} diff --git a/testapp-cordova/www/index.html b/testapp-cordova/www/index.html index cad3fb2..d537c56 100644 --- a/testapp-cordova/www/index.html +++ b/testapp-cordova/www/index.html @@ -33,17 +33,23 @@ PowerAuth Tests +

PowerAuth Tests

-

Starting Javascript

-

Javascript running

+

Loading javascript...

+

Javascript loaded

- +
+
+ +
+ +
+ +
-
+
From ea30914fc117e1692a41ccad647df617a62dbb67 Mon Sep 17 00:00:00 2001 From: Jan Kobersky Date: Wed, 11 Sep 2024 15:06:50 +0200 Subject: [PATCH 4/5] Changed order of exported objects --- gulpfile.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gulpfile.js b/gulpfile.js index ea74aba..d565180 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -143,6 +143,7 @@ const tmpDir = ".build"; // TODO: extract from the code const objectsToExport = [ "PowerAuth", + "PowerAuthError", "PowerAuthActivation", "PowerAuthAuthentication", "PowerAuthActivationCodeUtil", @@ -154,7 +155,6 @@ const tmpDir = ".build"; "PowerAuthBiometryType", "PowerAuthClientConfiguration", "PowerAuthConfiguration", - "PowerAuthError", "PowerAuthErrorCode", "PowerAuthKeychainConfiguration", "PowerAuthKeychainProtection", From b9bcab294a5bc26a2fe4de2133855cd8943ba147 Mon Sep 17 00:00:00 2001 From: Jan Kobersky Date: Wed, 11 Sep 2024 15:37:28 +0200 Subject: [PATCH 5/5] Exporting additional types --- gulpfile.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gulpfile.js b/gulpfile.js index d565180..e055268 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -158,6 +158,7 @@ const tmpDir = ".build"; "PowerAuthErrorCode", "PowerAuthKeychainConfiguration", "PowerAuthKeychainProtection", + "PowerAuthSharingConfiguration", "PowerAuthPassword", "BaseNativeObject", "PinTestIssue", @@ -165,6 +166,7 @@ const tmpDir = ".build"; "buildClientConfiguration", "buildBiometryConfiguration", "buildKeychainConfiguration", + "buildSharingConfiguration", "PowerAuthDebug", "NativeObjectRegister", ]