amplify-swift/AmplifyPlugins/Core/AWSPluginsCore/Auth/AWSAuthModeStrategy.swift

207 lines
6.9 KiB
Swift

//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
import Foundation
import Amplify
/// Represents different auth strategies supported by a client
/// interfacing with an AppSync backend
public enum AuthModeStrategyType {
/// Default authorization type read from API configuration
case `default`
/// Uses schema metadata to create a prioritized list of potential authorization types
/// that could be used for a request. The client iterates through that list until one of the
/// avaialable types succeeds or all of them fail.
case multiAuth
public func resolveStrategy() -> AuthModeStrategy {
switch self {
case .default:
return AWSDefaultAuthModeStrategy()
case .multiAuth:
return AWSMultiAuthModeStrategy()
}
}
}
/// Methods for checking user current status
public protocol AuthModeStrategyDelegate: AnyObject {
func isUserLoggedIn() async -> Bool
}
/// Represents an authorization strategy used by DataStore
public protocol AuthModeStrategy: AnyObject {
var authDelegate: AuthModeStrategyDelegate? { get set }
init()
func authTypesFor(schema: ModelSchema, operation: ModelOperation) async -> AWSAuthorizationTypeIterator
}
/// AuthorizationType iterator with an extra `count` property used
/// to predict the number of values
public protocol AuthorizationTypeIterator {
associatedtype AuthorizationType
init(withValues: [AuthorizationType])
/// Total number of values
var count: Int { get }
/// Next available `AuthorizationType` or `nil` if exhausted
mutating func next() -> AuthorizationType?
}
/// AuthorizationTypeIterator for values of type `AWSAuthorizationType`
public struct AWSAuthorizationTypeIterator: AuthorizationTypeIterator {
public typealias AuthorizationType = AWSAuthorizationType
private var values: IndexingIterator<[AWSAuthorizationType]>
private var _count: Int
public init(withValues values: [AWSAuthorizationType]) {
self.values = values.makeIterator()
self._count = values.count
}
public var count: Int {
_count
}
public mutating func next() -> AWSAuthorizationType? {
values.next()
}
}
// MARK: - AWSDefaultAuthModeStrategy
/// AWS default auth mode strategy.
///
/// Returns an empty AWSAuthorizationTypeIterator, so we can just rely on the default authorization type
/// registered as interceptor for the API
public class AWSDefaultAuthModeStrategy: AuthModeStrategy {
public weak var authDelegate: AuthModeStrategyDelegate?
required public init() {}
public func authTypesFor(schema: ModelSchema,
operation: ModelOperation) -> AWSAuthorizationTypeIterator {
return AWSAuthorizationTypeIterator(withValues: [])
}
}
// MARK: - AWSMultiAuthModeStrategy
/// Multi-auth strategy implementation based on schema metadata
public class AWSMultiAuthModeStrategy: AuthModeStrategy {
public weak var authDelegate: AuthModeStrategyDelegate?
private typealias AuthPriority = Int
required public init() {}
private static func defaultAuthTypeFor(authStrategy: AuthStrategy) -> AWSAuthorizationType {
var defaultAuthType: AWSAuthorizationType
switch authStrategy {
case .owner:
defaultAuthType = .amazonCognitoUserPools
case .groups:
defaultAuthType = .amazonCognitoUserPools
case .private:
defaultAuthType = .amazonCognitoUserPools
case .public:
defaultAuthType = .apiKey
case .custom:
defaultAuthType = .function
}
return defaultAuthType
}
/// Given an auth rule, returns the corresponding AWSAuthorizationType
/// - Parameter authRule: authorization rule
/// - Returns: returns corresponding AWSAuthorizationType or a default
private static func authTypeFor(authRule: AuthRule) -> AWSAuthorizationType {
if let authProvider = authRule.provider {
return authProvider.toAWSAuthorizationType()
}
return defaultAuthTypeFor(authStrategy: authRule.allow)
}
/// Given an auth rule strategy returns its corresponding priority.
/// Strategies are ordered from "most specific" to "least specific".
/// - Parameter authStrategy: auth rule strategy
/// - Returns: priority
private static func priorityOf(authStrategy: AuthStrategy) -> AuthPriority {
switch authStrategy {
case .custom:
return 0
case .owner:
return 1
case .groups:
return 2
case .private:
return 3
case .public:
return 4
}
}
/// Given an auth rule provider returns its corresponding priority.
/// Providers are ordered from "most specific" to "least specific".
/// - Parameter authRuleProvider: auth rule provider
/// - Returns: priority
private static func priorityOf(authRuleProvider provider: AuthRuleProvider) -> AuthPriority {
switch provider {
case .function:
return 0
case .userPools:
return 1
case .oidc:
return 2
case .iam:
return 3
case .apiKey:
return 4
}
}
/// A predicate used to sort Auth rules according to above priority rules
/// Use provider priority to sort if rules have the same strategy
private static let comparator = { (rule1: AuthRule, rule2: AuthRule) -> Bool in
if let providerRule1 = rule1.provider,
let providerRule2 = rule2.provider, rule1.allow == rule2.allow {
return priorityOf(authRuleProvider: providerRule1) < priorityOf(authRuleProvider: providerRule2)
}
return priorityOf(authStrategy: rule1.allow) < priorityOf(authStrategy: rule2.allow)
}
/// Returns the proper authorization type for the provided schema according to a set of priority rules
/// - Parameters:
/// - schema: model schema
/// - operation: model operation
/// - Returns: an iterator for the applicable auth rules
public func authTypesFor(schema: ModelSchema,
operation: ModelOperation) async -> AWSAuthorizationTypeIterator {
var applicableAuthRules = schema.authRules
.filter(modelOperation: operation)
.sorted(by: AWSMultiAuthModeStrategy.comparator)
// if there isn't a user signed in, returns only public or custom rules
if let authDelegate = authDelegate, await !authDelegate.isUserLoggedIn() {
applicableAuthRules = applicableAuthRules.filter { rule in
return rule.allow == .public || rule.allow == .custom
}
}
let applicableAuthTypes = applicableAuthRules.map {
AWSMultiAuthModeStrategy.authTypeFor(authRule: $0)
}
return AWSAuthorizationTypeIterator(withValues: applicableAuthTypes)
}
}