Onboarding - configuration

SAPFioriFlows uses SAPFoundation‘s ConfigurationProvider component to gather required configuration information. Since there are multiple configuration sources and format structures, SAPFioriFlows then uses transformer classes to transform this information into a unified format which can be digested easily by the steps. The WelcomeStep typically gets the configuration using the ConfigurationProvider, then performs the transformation using one of the configuration transformers.

Transforming the configuration

The default data supported is DiscoveryService JSON structure, and the default transformer of SAPFioriFlows is DiscoveryServiceConfigurationTransformer. For other structures or custom structures in the DiscoveryService use the ConfigurationTransformer class or develop your own transformer class that implements the ConfigurationTransforming protocol.

About transformers

ConfigurationTransformer transforms a [String: Any] configuration to typed objects and returns them in a [OnboardingInfoKey: Any]. It uses other ConfigurationTransforming instances for the transformation, making it a recursive transformer which can be used to process complex, deep configuration hierarchies. The transformation process iterates through the keys of the configuration Dictionary and looks up the transformers using that key (in the transormer dictionary). When a transformer is found under the specified key, the corresponding sub-configuration is passed to it for processing. All transformers’ result is merged internally together, and the final result is a flat Dictionary of [OnboardingInfoKey: Any]. The SDK ships other transformers to help the transforming of any configuration and of course it can be used with custom transformers as well.

An example: Given the following sample structure:

let configuration : [String: Any] = [
    "someURL": "https://www.sap.com",
    "someString": "An arbitrary string",
    "simpleStructure": [
        "firstStringField": "first string value",
        "secondURLField": "https://www.sap.com"
    ],
    "complexStructure": [
        "some.oauth.params.key": [
            "authorizationEndpointURL": "https://someoauthservice.sap.hana.ondemand.com/oauth2/api/v1/authorize",
            "clientID": "66962837-fd94-4dcd-acf7-ade549224e78",
            "redirectURL": "https://someoauthservice.int.sap.hana.ondemand.com",
            "tokenEndpointURL": "https://someoauthservice.int.sap.hana.ondemand.com/oauth2/api/v1/token"
        ],
        "some.saml.params.key": [
            "authenticationEndpoint": "https://somesamlservice.int.sap.hana.ondemand.com/SAMLAuthLauncher",
            "finishEndpoint": "https://somesamlservice.int.sap.hana.ondemand.com/SAMLAuthLauncher?finishEndpointParam=someUnusedValue"
        ],
        "DiscoveryServiceConfigForCertificate": [
            "com.sap.mobilesecure.certificateService.attributesEndpoint": "http://some.dummy.url",
            "com.sap.mobilesecure.certificateService.requestEndpoint": "http://some.dummy.url",
            "com.sap.mobilesecure.certificateService.retireEndpoint": "http://some.dummy.url",
            "com.sap.mobilesecure.certificateService.publicKeyPinSet": [],
            "com.sap.mobilesecure.certificateService.authType": [
                "type":"oauth",
                "authorizationEndpoint": "http://some.dummy.url",
                "tokenEndpoint": "http://some.dummy.url",
                "client_id": "1234",
                "redirect_uri": "http://some.dummy.url"
            ]
        ]
    ]
]

This configuration must be converted to a flat [OnboardingInfoKey: Any] dictionary where the values are appropriate types (struct, URL, String, etc). So as a result we have to get the following object:

let transformedDict : [OnboardingInfoKey: Any] = [
    .requiredURL: URL,
    .requiredString String,
    .simpleStructure: SimpleStructure,
    .oauth2Parameters: OAuth2AuthenticationParameters,
    .samlParameters: SAMLAuthenticationParameters,
    .oauth2ParamsForCertificate: OAuth2AuthenticationParameters,
    .discoveryServiceParametersForCertificate: SAPcpmsUserIdentityConfigurationParameters
]

To transform the source configuration dictionary to the expected result, the ConfigurationTransformer must be used with appropriate transformers mirroring the structure:

// let's describe the given configuration with a transformation where we specify the possible keys, values as:
//    source: typedtransformer( destination )

let configurationTransformers: [String: ConfigurationTransforming] = [
    "someURL": BaseTransformer(targetKey: .requiredURL, transformer: transformToURL),
    "someString": BaseTransformer(targetKey: .requiredString, transformer: transformToString),
    "simpleStructure": BaseTransformer(targetKey: .simpleStructure, transformer: SimpleStructure.init(customConfig:)),
    "complexStructure": DictionaryConfigurationTransformer(transformers: [
        "some.oauth.params.key": BaseTransformer(targetKey: .oauth2Parameters, transformer: OAuth2AuthenticationParameters.init(cloudPlatformConfig:)),
        "some.saml.params.key": BaseTransformer(targetKey: .samlParameters, transformer: SAMLAuthenticationParameters.init(cloudPlatformConfig:)),
        "DiscoveryServiceConfigForCertificate": CustomDiscoveryServiceConfigurationTransformer(key: .discoveryServiceParametersForCertificate, oauthKey: .oauth2ParamsForCertificate)
    ])
]

and use the transformer and the transformed data:

let configurationTransformer = ConfigurationTransformer(transformers: configurationTransformers)
do {
    let transformedDictionary = try configurationTransformer(config: configuration)
    if let simpleStructure = transformedDictionary[.simpleStructure] as? SimpleStructure {
        // do something
    }
}
catch {
    print("some error happened \(error)")
}

Of course as a prerequisite the OnboardingInfoKeys must be declared, and the used transformers must exists.

// specifying onboarding info keys: declared by the onboarding steps
public extension OnboardingInfoKey {
    public static let requiredURL = OnboardingInfoKey("requiredURLKey")
    public static let requiredString = OnboardingInfoKey("requiredStringKey")
    public static let simpleStructure = OnboardingInfoKey("simpleStructureKey")
    public static let oauth2Parameters = OnboardingInfoKey("oauth2ParametersKey")
    public static let samlParameters = OnboardingInfoKey("samlParametersKey")
    public static let oauth2ParamsForCertificate = OnboardingInfoKey("oauth2ParametersForCertificateKey")
    public static let discoveryServiceParametersForCertificate = OnboardingInfoKey("discoveryServiceParametersForCertificateKey")
}

// declaring a custom structure
public struct SimpleStructure {
    let firstStringField: String
    let secondURLField: URL
}

// writing a transformer for the structure which can be used directly or by the BaseTransformer with ConfigurationTransformer
public extension SimpleStructure {
    public init(customConfig config: Any) throws  {
        guard let simpleConfig = config as? [String: String] else {
            throw OnboardingError.invalidArgument("not a dictionary", config, source: "SimpleStructure")
        }
        guard let first = simpleConfig["firstStringField"], let secondStr = simpleConfig["secondURLField"] else {
            throw OnboardingError.missingArgument("first or second parameter missing while parsing SimpleStructure", source: "SimpleStructure")
        }
        let second = try transformToURL(secondStr)
        self.init(firstStringField: first, secondURLField: second)
    }
}

open class CustomDiscoveryServiceConfigurationTransformer: ConfigurationTransforming {
    let key : OnboardingInfoKey
    let oauthKey : OnboardingInfoKey
        public init(key : OnboardingInfoKey, oauthKey: OnboardingInfoKey) {
        self.key = key
        self.oauthKey = oauthKey
    }

    open func transform(config: Any) throws -> [OnboardingInfoKey: Any] {
        guard let dict = config as? [String:Any] else {
            throw OnboardingError.invalidArgument("not a dictionary", config, source: "CustomDiscoveryServiceConfigurationTransformer")
        }
        guard let params = SAPcpmsUserIdentityConfigurationParameters(certificateDiscoveryConfiguration: dict) else {
            throw  OnboardingError.invalidArgument("Cannot transform DiscoveryServiceConfigurationParser: Invalid structure", dict, source: "CustomDiscoveryServiceConfigurationTransformer")
        }
        guard let oauthParams = OAuth2AuthenticationParameters(certificateDiscoveryConfiguration: dict) else {
            throw  OnboardingError.missingArgument("Cannot transform DiscoveryServiceConfigurationParser: Invalid structure", source: "CustomDiscoveryServiceConfigurationTransformer")
        }

        return [key: params, oauthKey: oauthParams]// as [OnboardingInfoKey: Any]
    }
}

In the SDK the different onboarding steps declare their required keys they use for accessign information in the info field of the OnboardingContext. Several default transformers are available as public funcs or default initializers. Several structures which is an input parameter for a step in the Foundation framework implements a convenience initializer with discoveryServiceConfig: prefix which parse the default configuration declared by the SDK. For example:

  • BaseTransformer can be used with transformers resulting a single object, see a list of examples there.
  • DiscoveryServiceCertificateConfigurationTransformer
  • BaseTransformer it’s a helper class which can be used with ConfigurationTransformer to tranform a value to a typed object using any transformer method. The method can be a static func, a closure or an initializer.

    *There are several default transformers in the SDK which can be used as transformer

    • transformToURL
    • transformToValue: generic method for any base value type; can be used
    • OAuth2AuthenticationParameters.init(discoveryServiceConfig:)
    • SAMLAuthenticationParameters.init(discoveryServiceConfig:) Example structure: “`Swift let configuration : [String: Any] = [ "someExampleURL”: “https://www.sap.com”, “someString” : “An arbitrary string”, “settingsExchange” : [ “passwordPolicy”: [ “somePolicyProperty”:“someValue”, “somePolicyProperty2”:“someValue2”, ], “logSettings”: [ “someLogProperty”:“someValue”, “someLogProperty2”:“someValue2”, ] ] ]

    // The example transformationMap for the structure let sampleTransformationMap : [String: ConfigurationTransforming] = [ “someExampleURL” : BaseTransformer(targetKey: .requiredURL, transformer: transformToURL), “someString” : BaseTransformer(targetKey: .requiredString), “settingsExchange” : ConfigurationTransformer(transformationMap: [ “passwordPolicy” : BaseTransformer(targetKey: .passcodePolicy, transformer: FUIPasscodePolicy.init(sapcpmsSettingsPasswordPolicy:)), “logSettings” : BaseTransformer(targetKey: .sapcpmsLogSettings, transformer: SAPcpmsLogSettings.init(sapcpmsLogSettings:)) ]) ]

    See more

    Declaration

    Swift

    open class BaseTransformer<T> : ConfigurationTransforming
  • ConfigurationTransformer transforms a [String: Any] configuration to typed objects and returns them in a [OnboardingInfoKey: Any]. It uses other ConfigurationTransforming instances for the transformation, making it a recursive transformer which can be used to process complex, deep configuration hierarchies. The transformation process iterates through the keys of the configuration Dictionary and looks up the transformers using that key (in the transormer dictionary). When a transformer is found under the specified key, the corresponding sub-configuration is passed to it for processing. All transformers’ result is merged internally together, and the final result is a flat Dictionary of [OnboardingInfoKey: Any]. The SDK ships other transformers to help the transforming of any configuration and of course it can be used with custom transformers as well.

    An example: Given the following sample structure:

        let configuration : [String: Any] = [
            "someURL": "https://www.sap.com",
            "someString": "An arbitrary string",
            "simpleStructure": [
                "firstStringField": "first string value",
                "secondURLField": "https://www.sap.com"
            ],
            "complexStructure": [
                "some.oauth.params.key": [
                    "authorizationEndpointURL": "https://someoauthservice.sap.hana.ondemand.com/oauth2/api/v1/authorize",
                    "clientID": "66962837-fd94-4dcd-acf7-ade549224e78",
                    "redirectURL": "https://someoauthservice.int.sap.hana.ondemand.com",
                    "tokenEndpointURL": "https://someoauthservice.int.sap.hana.ondemand.com/oauth2/api/v1/token"
                ],
                "some.saml.params.key": [
                    "authenticationEndpoint": "https://somesamlservice.int.sap.hana.ondemand.com/SAMLAuthLauncher",
                    "finishEndpoint": "https://somesamlservice.int.sap.hana.ondemand.com/SAMLAuthLauncher?finishEndpointParam=someUnusedValue"
                ],
                "DiscoveryServiceConfigForCertificate": [
                    "com.sap.mobilesecure.certificateService.attributesEndpoint": "http://some.dummy.url",
                    "com.sap.mobilesecure.certificateService.requestEndpoint": "http://some.dummy.url",
                    "com.sap.mobilesecure.certificateService.retireEndpoint": "http://some.dummy.url",
                    "com.sap.mobilesecure.certificateService.publicKeyPinSet": [],
                    "com.sap.mobilesecure.certificateService.authType": [
                        "type":"oauth",
                        "authorizationEndpoint": "http://some.dummy.url",
                        "tokenEndpoint": "http://some.dummy.url",
                        "client_id": "1234",
                        "redirect_uri": "http://some.dummy.url"
                    ]
                ]
            ]
        ]
    

    This configuration must be converted to a flat [OnboardingInfoKey: Any] dictionary where the values are appropriate types (struct, URL, String, etc). So as a result we have to get the following object:

        let transformedDict : [OnboardingInfoKey: Any] = [
            .requiredURL: URL,
            .requiredString String,
            .simpleStructure: SimpleStructure,
            .oauth2Parameters: OAuth2AuthenticationParameters,
            .samlParameters: SAMLAuthenticationParameters,
            .oauth2ParamsForCertificate: OAuth2AuthenticationParameters,
            .discoveryServiceParametersForCertificate: SAPcpmsUserIdentityConfigurationParameters
        ]
    

    To transform the source configuration dictionary to the expected result, the ConfigurationTransformer must be used with appropriate transformers mirroring the structure:

        // let's describe the given configuration with a transformation where we specify the possible keys, values as:
        //    source: typedtransformer( destination )
        let configurationTransformers: [String: ConfigurationTransforming] = [
            "someURL": BaseTransformer(targetKey: .requiredURL, transformer: transformToURL),
            "someString": BaseTransformer(targetKey: .requiredString, transformer: transformToString),
            "simpleStructure": BaseTransformer(targetKey: .simpleStructure, transformer: SimpleStructure.init(customConfig:)),
            "complexStructure": DictionaryConfigurationTransformer(transformers: [
                "some.oauth.params.key": BaseTransformer(targetKey: .oauth2Parameters, transformer: OAuth2AuthenticationParameters.init(cloudPlatformConfig:)),
                "some.saml.params.key": BaseTransformer(targetKey: .samlParameters, transformer: SAMLAuthenticationParameters.init(cloudPlatformConfig:)),
                "DiscoveryServiceConfigForCertificate": CustomDiscoveryServiceConfigurationTransformer(key: .discoveryServiceParametersForCertificate, oauthKey: .oauth2ParamsForCertificate)
            ])
        ]
    

    and use the transformer and the transformed data:

        let configurationTransformer = ConfigurationTransformer(transformers: configurationTransformers)
        do {
            let transformedDictionary = try configurationTransformer(config: configuration)
            if let simpleStructure = transformedDictionary[.simpleStructure] as? SimpleStructure {
                // do something
            }
        }
        catch {
            print("some error happened \(error)")
        }
    

    Of course as a prerequisite the OnboardingInfoKeys must be declared, and the used transformers must exists.

        // specifying onboarding info keys: declared by the onboarding steps
        public extension OnboardingInfoKey {
            public static let requiredURL = OnboardingInfoKey("requiredURLKey")
            public static let requiredString = OnboardingInfoKey("requiredStringKey")
            public static let simpleStructure = OnboardingInfoKey("simpleStructureKey")
            public static let oauth2Parameters = OnboardingInfoKey("oauth2ParametersKey")
            public static let samlParameters = OnboardingInfoKey("samlParametersKey")
            public static let oauth2ParamsForCertificate = OnboardingInfoKey("oauth2ParametersForCertificateKey")
            public static let discoveryServiceParametersForCertificate = OnboardingInfoKey("discoveryServiceParametersForCertificateKey")
        }
    
        // declaring a custom structure
        public struct SimpleStructure {
            let firstStringField: String
            let secondURLField: URL
        }
    
        // writing a transformer for the structure which can be used directly or by the BaseTransformer with ConfigurationTransformer
        public extension SimpleStructure {
            public init(customConfig config: Any) throws  {
                guard let simpleConfig = config as? [String: String] else {
                    throw OnboardingError.invalidArgument("not a dictionary", config, source: "SimpleStructure")
                }
                guard let first = simpleConfig["firstStringField"], let secondStr = simpleConfig["secondURLField"] else {
                    throw OnboardingError.missingArgument("first or second parameter missing while parsing SimpleStructure", source: "SimpleStructure")
                }
                let second = try transformToURL(secondStr)
                self.init(firstStringField: first, secondURLField: second)
            }
        }
    
        open class CustomDiscoveryServiceConfigurationTransformer: ConfigurationTransforming {
            let key : OnboardingInfoKey
            let oauthKey : OnboardingInfoKey
            public init(key : OnboardingInfoKey, oauthKey: OnboardingInfoKey) {
                self.key = key
                self.oauthKey = oauthKey
            }
    
            open func transform(config: Any) throws -> [OnboardingInfoKey: Any] {
                guard let dict = config as? [String:Any] else {
                    throw OnboardingError.invalidArgument("not a dictionary", config, source: "CustomDiscoveryServiceConfigurationTransformer")
                }
                guard let params = SAPcpmsUserIdentityConfigurationParameters(certificateDiscoveryConfiguration: dict) else {
                    throw  OnboardingError.invalidArgument("Cannot transform DiscoveryServiceConfigurationParser: Invalid structure", dict, source: "CustomDiscoveryServiceConfigurationTransformer")
                }
                guard let oauthParams = OAuth2AuthenticationParameters(certificateDiscoveryConfiguration: dict) else {
                    throw  OnboardingError.missingArgument("Cannot transform DiscoveryServiceConfigurationParser: Invalid structure", source: "CustomDiscoveryServiceConfigurationTransformer")
                }
    
                return [key: params, oauthKey: oauthParams]// as [OnboardingInfoKey: Any]
            }
        }
    

    In the SDK the different onboarding steps declare their required keys they use for accessign information in the info field of the OnboardingContext. Several default transformers are available as public funcs or default initializers. Several structures which is an input parameter for a step in the Foundation framework implements a convenience initializer with discoveryServiceConfig: prefix which parse the default configuration declared by the SDK.

    For example:
    
    • BaseTransformer can be used with transformers resulting a single object, see a list of examples there.
    • DiscoveryServiceCertificateConfigurationTransformer
    See more

    Declaration

    Swift

    open class ConfigurationTransformer : ConfigurationTransforming
  • This protocol describes a transformation from a specific value to an other object. It is used to transform a configuration to typed objects. The config source of the transformation can be a Dictionary, an Array or any simple type. Since the config can be transformed to more objects (if it is a Dictionary describing more structures) the result is a dictionary which must contain the transformed typed objects. The keys in the result dictionary must be declared first to avoid typos and provide a clear declared way of key usage

    See more

    Declaration

    Swift

    public protocol ConfigurationTransforming : AnyObject
  • Transforms the Discovery Service configuration structure. Creates more objects using the structure: - SAPcpmsSettingsParameters - authenticationURL - SAMLAuthenticationParameters - OAuth2AuthenticationParameters

    The structure:

    {
      "auth": [{
          "type": <#String#>,
          "config": <#[String: String]#>
      }],
      "host": "<#host URL string#>",
      "port": 443,
      "protocol": "https"
    }
    
    See more

    Declaration

    Swift

    open class DiscoveryServiceConfigurationTransformer : ConfigurationTransforming
  • Default transformation map structure for transforming downloaded SAPcpmsSettings like logSettings and PasswordPolicy

    Declaration

    Swift

    public var SAPcpmsSettingsTransformationMap: [String : ConfigurationTransforming]
  • Transforms the DisoveryService configuration data to a SAPcpmsUserIdentityConfigurationParameters and an OAuth2AuthenticationParameters and returns with a [OnboardingInfoKey: Any] dictionary associating the key parameter with the SAPcpmsUserIdentityConfigurationParameters and the oauthKey with the OAuth2AuthenticationParameters

    • The structure in the dictionary should follow this JSON structure:
    {
        "com.sap.mobilesecure.certificateService.attributesEndpoint":"",
        "com.sap.mobilesecure.certificateService.requestEndpoint":"",
        "com.sap.mobilesecure.certificateService.retireEndpoint":"",
        "com.sap.mobilesecure.certificateService.publicKeyPinSet":[],
        "com.sap.mobilesecure.certificateService.authType":{
            "type":"oauth",
            "authorizationEndpoint":"",
            "tokenEndpoint":"",
            "client_id":"",
            "redirect_uri":""
        }
    }
    
    See more

    Declaration

    Swift

    open class SAPcpmsUserIdentityDiscoveryConfigurationTransformer : ConfigurationTransforming
  • Transforms the input to a URL. Throws OnboardingError.invalidArgument if fails

    Throws

    OnboardingError.invalidArgument

    Declaration

    Swift

    public func transformToURL(_ object: Any) throws -> URL

    Parameters

    object

    the object which should be tranformed to URL, must be a String which contains a valid URL

    Return Value

    the URL

  • Transforms an object to a value with optional cast

    Throws

    OnboardingError.invalidArgument

    Declaration

    Swift

    public func transformToValue<T>(_ object: Any) throws -> T

    Parameters

    object

    the object to tranform to a typed value

    Return Value

    the same object casted to the given T type