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 OnboardingInfoKey
s 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 withConfigurationTransformer
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 usedOAuth2AuthenticationParameters.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 moreDeclaration
Swift
open class BaseTransformer<T> : ConfigurationTransforming
-
ConfigurationTransformer
transforms a[String: Any]
configuration to typed objects and returns them in a[OnboardingInfoKey: Any]
. It uses otherConfigurationTransforming
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
OnboardingInfoKey
s 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
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
See moreconfig
source of the transformation can be a Dictionary, an Array or any simple type. Since theconfig
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 usageDeclaration
Swift
public protocol ConfigurationTransforming : AnyObject
-
Transforms the Discovery Service configuration structure. Creates more objects using the structure: - SAPcpmsSettingsParameters - authenticationURL - SAMLAuthenticationParameters - OAuth2AuthenticationParameters
The structure:
See more{ "auth": [{ "type": <#String#>, "config": <#[String: String]#> }], "host": "<#host URL string#>", "port": 443, "protocol": "https" }
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 anOAuth2AuthenticationParameters
and returns with a[OnboardingInfoKey: Any]
dictionary associating thekey
parameter with theSAPcpmsUserIdentityConfigurationParameters
and theoauthKey
with theOAuth2AuthenticationParameters
- 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":"" } }
Declaration
Swift
open class SAPcpmsUserIdentityDiscoveryConfigurationTransformer : ConfigurationTransforming
-
Transforms the input to a URL. Throws OnboardingError.invalidArgument if fails
Throws
OnboardingError.invalidArgumentDeclaration
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.invalidArgumentDeclaration
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