Onboarding - sharing

Sharing

An application can store and share data locally or among applications. The store configuration depends on how many stores the application uses.

Possible use cases, depending on how many stores are used simultaneously in the onboarding flow:

  • one store
    • local default store - no further configuration needed (default case, no sharing)
    • local shared store
    • shared among applications
  • more stores: one store usually assigned to the onboarding session, the rest of them are common
    • all stores are local
    • one or more are shared

Sharing a store

By default, the onboardingID (from the onboarding context) is used as the store ID, creating uniqe store names. In order to access a shared store, you need to use the same store ID, independently from the onbaording. You must assign a custom UUID to the storeID property on the StoreManagerStep, before onboarding / restoring. This is the ID you must use in the other applications that want to access this store.

There are more options to use the same store ID:

  • when the first store is created, store its ID in a place where it is visible for the other users
  • use a generated ID
  • use harcoded UUID
let storeManagerStep = StoreManagerStep()
let uuid: UUID = <#Shared ID#>
storeManagerStep.storeID = uuid

Sharing configuration among users

The configuration sharing is available among users. There can be any number of stores, the example uses two. All stores can have passcode. All stores can present passcode screens or should be use a hard coded passcode. The current example and convenience mode is to use the shared stores as unencrypted

  • The first store is an unencrypted one which is used to store the configuration. It is shared amongst multiple users who want to share the same configuration.
  • The second store is protected by the passcode and its encryption is based on the passcode policy. This not be modified and all other steps can be used as originally.

The preferred shared store is the CredentialStore which is part of the context. The flows should use the same CredentialStoreID for initializing the shared credential store. Each user should use the same CredentialStore – where the configuration is stored. CredentialStoreID.shared is a pre-defined identifier specifically for the purpose of sharing stores. The StoreSelectorStep wraps an OnboardingStep instance for custom credential store “injection”. You can create STORE-ID associations in the OnbordingContext. The store selector selects the store with the corresponding ID and inject into the credentialStore property for the given step to use. A simple onboarding flow with the configured StoreManagerStep and WelcomeScreenStep:

var onboardingSteps: [OnboardingStep] {
    return [
        StoreSelectorStep(step: StoreManagerStep(), credentialStoreID: .shared),
        StoreSelectorStep(step: WelcomeScreenStep(), credentialStoreID: .shared),
        CompositeStep(steps: SAPcpmsDefaultSteps.configuration),
        OAuth2AuthenticationStep(),
        CompositeStep(steps: SAPcpmsDefaultSteps.settingsDownload),
        CompositeStep(steps: SAPcpmsDefaultSteps.applyDuringOnboard),
        UserConsentStep(userConsentFormsContent: []),
        StoreManagerStep()
    ]
}

Sharing among applications

Prerequisites: Sharing a store.

The SDK currently supports data sharing among applications via the keychain only. To use the keychain feature provided by the OS, please enable the App Groups capability under your Project File -> Capabilities and provide your app group name - this will be used as keychainAccessGroup later on.

In order to create a keychain store using the StoreManagerStep, you have to pass an instance of KeychainStoreManager to the step’s initializer.

let keychainAccessGroup: String = <#Keychain access group name#>
let storeManager = KeychainStoreManager(keychainAccessGroup: keychainAccessGroup)

let storeManagerStep = StoreManagerStep(persistentStoreManager: storeManager)

Note

The StoreManagerStep persists state information into the keychain. By default it uses the accessGroup property of the KeychainStoreManager to store the information in a shared manner. If you use another implementation then you have to set the accessGroup property on the StoreManagerStep instance by yourself.

The StoreManagerStep onboard method fails if the store already exists. Using a shared store, this should not be handled as error because another application could create the store. To bypass this behavior of the step, set the below runRestoreIfStoreExists boolean flag to true.

storeManagerStep.runRestoreIfStoreExists = true

The above created StoreManagerStep now is able to create a shared storage, which can be accessed from other applications (with the same StoreManagerStep configuration).

Using more stores

Prerequisites: Sharing a store.

An application is not restricted to use one store only, it can create as many stores as it needs. By default, one composite storage is created when the onboarding context is initialized - this is the default store.

let onboardingContext = OnboardingContext()
// is equal to
let onboardingContext = OnboardingContext(credentialStore: CompositeStorage())

The context has a composite storage, which by default works as a memory storage for all the steps until a StoreManagerStep creates a persistent storage and persist all data in it. When you want to create additional stores, you must create another such composite storage for temporary use, and register it on the onboarding context for the steps to access. The concept of the temporary store is important, because the step that can create the actual store comes later in the flow.

let additionalStore = CompositeStorage()
let additionalStoreID = CredentialStoreID("additionalStore")
onboardingContext.credentialStores[additionalStoreID] = additionalStoreID

The additional store now is available on the onboarding context and the steps can access it if they know the label. However, we want to keep the step implementations independent from the store usage. In order to achieve this, all steps that use a store different than the default store, must be wrapped in a StoreSelectorStep instance. This selector instance ensures that the desired store is injected into the credentialStore property of the OnboardingContext before the given step runs.

let customStep: OnboardingStep = <#OnboardingStep#>
let selectorStep = StoreSelectorStep(step: customStep, credentialStoreID: additionalStoreID)

let flow: [OnboardingStep] = [..., selectorStep, ...]

Keep in mind, that not only the steps which use the new store should be wrapped, but also the StoreManagerStep which creates the persistent store for it.

let customStoreManagerStep = StoreManagerStep()
let storeSelectorStep = StoreSelectorStep(step:  customStoreManagerStep, credentialStoreID: additionalStoreID)

The passcode policy

When you use the StoreManagerStep for store creation, you have the option to apply passcode policy for your users. The step expects the OnboardingInfoKey of the passcode policy to be used, which has a default value. When you create multiple stores, there is an option that you need to use a different passcode policy (or none) for the other stores. Please provide a custom key for the steps that manages the other stores to “divert” the passcode policy search.

let onboardingInfoKey = <#OnboardingInfoKey#>
let otherStoreManagerStep = StoreManagerStep(passcodePolicyInfoKey: onboardingInfoKey)
  • Implementers must call the applyContext first than the PersistentStoreManaging protocols. The create/open/remove methods works only after it.

    See more

    Declaration

    Swift

    public protocol ContextSupporting : AnyObject
  • Credential Store ID. Use this struct for the StoreSelectorStep and OnbordingContext to create STORE - ID associations for steps that need to use a store other than the one in the OnboardingContext. You can extend the available items using extension.

    See more

    Declaration

    Swift

    public struct CredentialStoreID : RawRepresentable, Hashable, CustomStringConvertible, CustomDebugStringConvertible
  • KeychainStoreManager implementation of PersistentStoreManaging Creates/opens/removes KeychainStorage instances. The used cipherHandler provides the Cipher to used for encryption. When there is no Cipher then the data won’t be enctypted.

    See more

    Declaration

    Swift

    open class KeychainStoreManager : PersistentStoreManaging
  • Action, which should be given to StoreManager to start a passcode change flow

    See more

    Declaration

    Swift

    public enum PasscodeAction
  • Enum to represent passcode states on StoreManager

    See more

    Declaration

    Swift

    public enum PasscodeState
  • Implementers must implement this for manage the persistent store.

    See more

    Declaration

    Swift

    public protocol PersistentStoreManaging : AnyObject
  • SecureKeyValueStoreManager implementation of PersistentStoreManaging

    See more

    Declaration

    Swift

    open class SecureKeyValueStoreManager : PersistentStoreManaging, ContextSupporting
  • Handles all the available operations on the application’s credentialstore:

    • create store
    • open store
    • remove store
    • change encryption of store The store instance should be an implementation of the PersistentStoreManaging protocol StoreManager uses multiple screens and passcode/TouchID/FaceID functionalities from SAPFiori.

    A StoreManager instance is initialized in StoreManagerStep during onboarding with the required parameters in the StoreManager initializer, which then starts a createStore, which throws FUIPasscodeCreateController from SAPFiori. If the device supports a biometric identifier, such as Touch ID or Face ID and the downloaded passcode policy also allows it, the Touch ID / Face ID screen displays after the passcode creation screens. The user can decide whether or not to use biometrics in the application. When setting the passcode, certain information is stored in the keychain under the application’s default accessGroup. If the developer sets an accessGroup for the StoreManager, the accessGroup must be set, and in every case a storeManager is instantiated later.

    There is one exception where the passcode create screen is not shown while onboarding: If the developer does not set the defaultPasscodepolicy on StoreManagerStep, AND the policies are not assigned to the application on the server. Because of this, the passcode is turned off for the application and does not ask for a passcode when it opens later. This is less secure, and is not recommended. Note: The defaultPasscodePolicy is set to nil by default. If the defaultPasscodePolicy is set to the developers own default passcode policy, that becomes the requirement for entering the passcode whether or not the passcode policies assigned to the application on the server.

    In the restore case, the StoreManagerStep calls the StoreManager’s openStore function which, if a passcode was previously set, presents a FUIPasscodeInputController from SAPFiori. If the user enters an incorrect passcode, the retry limit decrements. If the retry limit reaches zero, the store is locked and all information is lost. The user must onboard again, after resetting the application. If the entered passcode is correct, then the restore flow starts with the previously defined steps.

    PasscodePolicyApplyStep is another step that can call changePasscodePolicy on StoreManager: During the restore flow, if the actual settings are downloaded from the server and the passcode policy changes, PasscodePolicyApplyStep calls the changePasscodePolicy(to newPolicy:completionHandler:) on StoreManager, which triggers a passcode change. This change is mandatory and cannot be cancelled. The change is triggered in every case where the server administrator sets a policy that is either weaker or stronger. StoreManager uses a customized FUIFeedbackScreen from SAPFiori to inform the user of any restrictions or additional possibilities regarding setting the passcode policy. The reason for the passcode change is described on that presented screen. The passcode change flow starts after the user taps the ‘Continue’ button.

    Possible passcode change flows:

    • No passcode -> passcode ( Turn on passcode, so passcode creation flow without the option to set Face/Touch ID.)
    • No passcode -> passcode with Touch/Face ID (Turn on passcode with biometric ID, so passcode creation flow with the option to enable Face/Touch ID.)
    • Passcode -> Passcode (Passcode change flow, the user has to enter the current passcode again, then set the new passcode and confirm it.)
    • Passcode -> No passcode (Turn off passcode, the user will be notified about the change, has nothing to do. The passcode will be turned off.)
    • Passcode -> Passcode with Touch/Face ID (Starting the automatic change flow will be supported in a later release, currently the Touch/Face ID availability will be added to the ‘availableChangePasscodeOptions’, so manually can be turned on if there is an application screen for setting these options.)
    • Passcode with Touch/Face ID -> Passcode (Turn off Touch/Face ID, the user will be notified about the restriction and can not use the biometric ID from that point to unlock the application.)
    • Passcode with Touch/Face ID -> No passcode (Turn off passcode, the user will be notified about the change, has nothing to do. The passcode and Touch/Face ID will be turned off.)

    Developers can provide a screen similar to Settings, where the user can change their own passcode depending on passcode policy and device capabilities: The ‘availablePasscodeActions’ returns an array, which contains the passcode actions which can be performed on StoreManager’s store. This list contains elements depending on passcode policy and device capabilities. After checking the availablePasscodeActions, the developer can call the performPasscodeAction(_ action:completionHandler:) function with the correct PasscodeAction. In this case one of the possible passcode change flows starts, the only difference is that a notification screen does not display by default.

    See more

    Declaration

    Swift

    public class StoreManager : FUIPasscodeValidationDelegate, FUIPasscodeControllerDelegate
  • Enum to handle errors in StoreManagerStep

    See more

    Declaration

    Swift

    public enum StoreManagerError : Error
    extension StoreManagerError: SAPError
  • Manages the persistent store in the onboarding flow Creates and opens a SecureKeyValueStore instance in the folder specified by SecureStoreFolderPath parameter or in applicationSupportDirectory when not specified including the OnboardingID in the file name. The StoreManagerStep uses multiple screens from SAPFiori, for example TouchID and Passcode screens. The texts on these screens are also customizable. The localizable UI components are grouped in FUIPasscodeController. The following example code snippet describes how to change/localize elements of StoreManagerStep

    Customization

    func customStoreManagerStep() -> OnboardingStep {
    
       let storeManagerStepBundle = Bundle(for: StoreManagerStep.self)
       let localizedChoosePasscodeTitleString = NSLocalizedString("StoreManagerChoosePasscodeTitleKey", tableName: "ExampleTableName", bundle: storeManagerStepBundle, value: "Example choose passcode text", comment: "")
       let localizedConfirmPasscodeString = NSLocalizedString("StoreManagerConfirmPasscodeKey", tableName: "ExampleTableName", bundle: storeManagerStepBundle, value: "Example confirm passcode text", comment: "")
       let localizedLearnMoreTouchIDLinkString = NSLocalizedString("StoreManagerLearnMoreTouchIDLinkKey", tableName: "ExampleTableName", bundle: storeManagerStepBundle, value: "Example learn more about touch id text", comment: "")
       let localizedSetPasscodeMessageFormat = NSLocalizedString("StoreManagerSetPasscodeMessageFormatKey", tableName: "ExampleTableName", bundle: storeManagerStepBundle, value: "Example set passcode message", comment: "")
       // ... localizations for other components
    
       FUIPasscodeController.choosePasscodeTitleString = localizedChoosePasscodeTitleString
       FUIPasscodeController.confirmPasscodeString = localizedConfirmPasscodeString
       FUIPasscodeController.learnMoreTouchIDLinkString = localizedLearnMoreTouchIDLinkString
       FUIPasscodeController.setPasscodeMessageFormat = localizedSetPasscodeMessageFormat
       // ... setting localization of other components ...
       return StoreManagerStep()
    
    } ```
    
    See more

    Declaration

    Swift

    open class StoreManagerStep : OnboardingStep
  • Use this class to wrap an OnboardingStep instance for custom credential store “injection”. You can create STORE - ID associations in the OnbordingContext, the flow controller will inject the store with the matching ID to the credentialStore property for this step to use.

    See more

    Declaration

    Swift

    open class StoreSelectorStep : OnboardingStep