amplify-swift/AmplifyPlugins/Auth/Tests/AuthHostApp/AuthIntegrationTests/Helpers/AuthSessionHelper.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: "")
}
}