195 lines
8.5 KiB
Swift
195 lines
8.5 KiB
Swift
//
|
|
// Copyright Amazon.com Inc. or its affiliates.
|
|
// All Rights Reserved.
|
|
//
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
//
|
|
|
|
@testable import Amplify
|
|
@testable import AWSCognitoAuthPlugin
|
|
import AWSPluginsCore
|
|
@_spi(KeychainStore) import AWSPluginsCore
|
|
import CryptoKit
|
|
import Foundation
|
|
import XCTest
|
|
|
|
struct AuthSessionHelper {
|
|
|
|
static func getCurrentAmplifySession(
|
|
shouldForceRefresh: Bool = false,
|
|
for testCase: XCTestCase,
|
|
with timeout: TimeInterval) async throws -> AWSAuthCognitoSession? {
|
|
var cognitoSession: AWSAuthCognitoSession?
|
|
let session = try await Amplify.Auth.fetchAuthSession(options: .init(forceRefresh: shouldForceRefresh))
|
|
cognitoSession = (session as? AWSAuthCognitoSession)
|
|
XCTAssertTrue(session.isSignedIn, "Session state should be signed In")
|
|
return cognitoSession
|
|
}
|
|
|
|
static func clearSession() {
|
|
let store = KeychainStore(service: "com.amplify.awsCognitoAuthPlugin")
|
|
try? store._removeAll()
|
|
}
|
|
|
|
static func invalidateSession(with amplifyConfiguration: AmplifyConfiguration) {
|
|
let configuration = getAuthConfiguration(configuration: amplifyConfiguration)
|
|
let credentialStore = AWSCognitoAuthCredentialStore(authConfiguration: configuration, accessGroup: nil)
|
|
guard let credentials = try? credentialStore.retrieveCredential() else {
|
|
return
|
|
}
|
|
switch credentials {
|
|
case .userPoolAndIdentityPool(signedInData: let signedInData,
|
|
identityID: let identityID,
|
|
credentials: let awsCredentials):
|
|
let updatedToken = updateTokenWithPastExpiry(signedInData.cognitoUserPoolTokens)
|
|
let signedInData = SignedInData(
|
|
signedInDate: signedInData.signedInDate,
|
|
signInMethod: signedInData.signInMethod,
|
|
cognitoUserPoolTokens: updatedToken)
|
|
let updatedCredentials = AmplifyCredentials.userPoolAndIdentityPool(
|
|
signedInData: signedInData,
|
|
identityID: identityID,
|
|
credentials: awsCredentials)
|
|
try! credentialStore.saveCredential(updatedCredentials)
|
|
case .userPoolOnly(signedInData: let signedInData):
|
|
let updatedToken = updateTokenWithPastExpiry(signedInData.cognitoUserPoolTokens)
|
|
let signedInData = SignedInData(
|
|
signedInDate: signedInData.signedInDate,
|
|
signInMethod: signedInData.signInMethod,
|
|
cognitoUserPoolTokens: updatedToken)
|
|
let updatedCredentials = AmplifyCredentials.userPoolOnly(signedInData: signedInData)
|
|
try! credentialStore.saveCredential(updatedCredentials)
|
|
default: break
|
|
}
|
|
|
|
}
|
|
|
|
static private func updateTokenWithPastExpiry(_ tokens: AWSCognitoUserPoolTokens)
|
|
-> AWSCognitoUserPoolTokens {
|
|
var idToken = tokens.idToken
|
|
var accessToken = tokens.accessToken
|
|
if var idTokenClaims = try? AWSAuthService().getTokenClaims(tokenString: idToken).get(),
|
|
var accessTokenClaims = try? AWSAuthService().getTokenClaims(tokenString: accessToken).get() {
|
|
|
|
idTokenClaims["exp"] = String(Date(timeIntervalSinceNow: -3000).timeIntervalSince1970) as AnyObject
|
|
accessTokenClaims["exp"] = String(Date(timeIntervalSinceNow: -3000).timeIntervalSince1970) as AnyObject
|
|
idToken = CognitoAuthTestHelper.buildToken(for: idTokenClaims)
|
|
accessToken = CognitoAuthTestHelper.buildToken(for: accessTokenClaims)
|
|
}
|
|
return AWSCognitoUserPoolTokens(idToken: idToken,
|
|
accessToken: accessToken,
|
|
refreshToken: "invalid",
|
|
expiration: Date().addingTimeInterval(-50000))
|
|
}
|
|
|
|
static private func getAuthConfiguration(configuration: AmplifyConfiguration) -> AuthConfiguration {
|
|
let jsonValueConfiguration = configuration.auth!.plugins["awsCognitoAuthPlugin"]!
|
|
let userPoolConfigData = parseUserPoolConfigData(jsonValueConfiguration)
|
|
let identityPoolConfigData = parseIdentityPoolConfigData(jsonValueConfiguration)
|
|
return try! authConfiguration(userPoolConfig: userPoolConfigData,
|
|
identityPoolConfig: identityPoolConfigData)
|
|
}
|
|
|
|
static private func parseUserPoolConfigData(_ config: JSONValue) -> UserPoolConfigurationData? {
|
|
// TODO: Use JSON serialization here to convert.
|
|
guard let cognitoUserPoolJSON = config.value(at: "CognitoUserPool.Default") else {
|
|
Amplify.Logging.info("Could not find Cognito User Pool configuration")
|
|
return nil
|
|
}
|
|
guard case .string(let poolId) = cognitoUserPoolJSON.value(at: "PoolId"),
|
|
case .string(let appClientId) = cognitoUserPoolJSON.value(at: "AppClientId"),
|
|
case .string(let region) = cognitoUserPoolJSON.value(at: "Region")
|
|
else {
|
|
return nil
|
|
}
|
|
|
|
var clientSecret: String?
|
|
if case .string(let clientSecretFromConfig) = cognitoUserPoolJSON.value(at: "AppClientSecret") {
|
|
clientSecret = clientSecretFromConfig
|
|
}
|
|
return UserPoolConfigurationData(poolId: poolId,
|
|
clientId: appClientId,
|
|
region: region,
|
|
clientSecret: clientSecret)
|
|
}
|
|
|
|
static private func parseIdentityPoolConfigData(_ config: JSONValue) -> IdentityPoolConfigurationData? {
|
|
|
|
guard let cognitoIdentityPoolJSON = config.value(at: "CredentialsProvider.CognitoIdentity.Default") else {
|
|
Amplify.Logging.info("Could not find Cognito Identity Pool configuration")
|
|
return nil
|
|
}
|
|
guard case .string(let poolId) = cognitoIdentityPoolJSON.value(at: "PoolId"),
|
|
case .string(let region) = cognitoIdentityPoolJSON.value(at: "Region")
|
|
else {
|
|
return nil
|
|
}
|
|
return IdentityPoolConfigurationData(poolId: poolId, region: region)
|
|
}
|
|
|
|
static private func authConfiguration(userPoolConfig: UserPoolConfigurationData?,
|
|
identityPoolConfig: IdentityPoolConfigurationData?) throws -> AuthConfiguration {
|
|
|
|
if let userPoolConfigNonNil = userPoolConfig, let identityPoolConfigNonNil = identityPoolConfig {
|
|
return .userPoolsAndIdentityPools(userPoolConfigNonNil, identityPoolConfigNonNil)
|
|
}
|
|
if let userPoolConfigNonNil = userPoolConfig {
|
|
return .userPools(userPoolConfigNonNil)
|
|
}
|
|
if let identityPoolConfigNonNil = identityPoolConfig {
|
|
return .identityPools(identityPoolConfigNonNil)
|
|
}
|
|
// Could not get either Userpool or Identitypool configuration
|
|
// Throw an error to stop the configure flow.
|
|
throw AuthError.configuration(
|
|
"Error configuring \(String(describing: self))",
|
|
AuthPluginErrorConstants.configurationMissingError
|
|
)
|
|
}
|
|
}
|
|
|
|
struct CognitoAuthTestHelper {
|
|
|
|
/// Helper to build a JWT Token
|
|
static func buildToken(for payload: [String: AnyObject]) -> String {
|
|
|
|
struct Header: Encodable {
|
|
let alg = "HS256"
|
|
let typ = "JWT"
|
|
}
|
|
|
|
// target dict
|
|
var dictionary = [String: String]()
|
|
for (key, value) in payload {
|
|
if let value = value as? String { dictionary[key] = value }
|
|
}
|
|
|
|
let secret = "256-bit-secret"
|
|
let privateKey = SymmetricKey(data: Data(secret.utf8))
|
|
|
|
let headerJSONData = try! JSONEncoder().encode(Header())
|
|
let headerBase64String = headerJSONData.urlSafeBase64EncodedString()
|
|
|
|
let payloadJSONData = try! JSONEncoder().encode(dictionary)
|
|
let payloadBase64String = payloadJSONData.urlSafeBase64EncodedString()
|
|
|
|
let toSign = Data((headerBase64String + "." + payloadBase64String).utf8)
|
|
|
|
let signature = HMAC<SHA256>.authenticationCode(for: toSign, using: privateKey)
|
|
let signatureBase64String = Data(signature).urlSafeBase64EncodedString()
|
|
|
|
let token = [headerBase64String, payloadBase64String, signatureBase64String].joined(separator: ".")
|
|
|
|
return token
|
|
}
|
|
}
|
|
|
|
fileprivate extension Data {
|
|
func urlSafeBase64EncodedString() -> String {
|
|
return base64EncodedString()
|
|
.replacingOccurrences(of: "+", with: "-")
|
|
.replacingOccurrences(of: "/", with: "_")
|
|
.replacingOccurrences(of: "=", with: "")
|
|
}
|
|
}
|