From 68ed2877a8203e26173dd8d04c1eabef713d9ac1 Mon Sep 17 00:00:00 2001 From: Desu Sai Venkat <48179357+desusai7@users.noreply.github.com> Date: Fri, 5 Jul 2024 17:08:38 +0530 Subject: [PATCH] feat: added support for visionOS platform (#859) --- .github/workflows/main.yml | 4 +- Auth0.podspec | 16 +- Auth0.xcodeproj/project.pbxproj | 873 +++++++++++++++++- .../contents.xcworkspacedata | 2 +- .../xcschemes/Auth0.visionOS.xcscheme | 67 ++ Auth0/ASProvider.swift | 2 +- Auth0/Auth0WebAuth.swift | 4 +- Auth0/MobileWebAuth.swift | 21 +- Auth0/Telemetry.swift | 2 + Auth0Tests/ASProviderSpec.swift | 39 +- Auth0Tests/Auth0Spec.swift | 2 +- Auth0Tests/AuthenticationErrorSpec.swift | 267 +++--- Auth0Tests/AuthenticationSpec.swift | 790 ++++++++-------- Auth0Tests/BioAuthenticationSpec.swift | 8 +- Auth0Tests/ChallengeGeneratorSpec.swift | 2 +- Auth0Tests/ClaimValidatorsSpec.swift | 2 +- Auth0Tests/ClearSessionTransactionSpec.swift | 2 +- Auth0Tests/CredentialsManagerErrorSpec.swift | 2 +- Auth0Tests/CredentialsManagerSpec.swift | 316 +++---- Auth0Tests/CredentialsSpec.swift | 2 +- .../IDTokenSignatureValidatorSpec.swift | 35 +- Auth0Tests/IDTokenValidatorBaseSpec.swift | 16 +- Auth0Tests/IDTokenValidatorSpec.swift | 57 +- Auth0Tests/JWKSpec.swift | 2 +- Auth0Tests/JWTAlgorithmSpec.swift | 2 +- Auth0Tests/LoggerSpec.swift | 2 +- Auth0Tests/LoginTransactionSpec.swift | 6 +- Auth0Tests/ManagementErrorSpec.swift | 2 +- Auth0Tests/ManagementSpec.swift | 16 +- Auth0Tests/Matchers.swift | 387 ++++---- Auth0Tests/NetworkStub.swift | 24 + Auth0Tests/OAuth2GrantSpec.swift | 64 +- Auth0Tests/RequestSpec.swift | 51 +- Auth0Tests/ResponseSpec.swift | 2 +- Auth0Tests/Responses.swift | 70 +- Auth0Tests/SafariProviderSpec.swift | 52 +- Auth0Tests/StubURLProtocol.swift | 46 + Auth0Tests/TelemetrySpec.swift | 10 +- Auth0Tests/TransactionStoreSpec.swift | 2 +- Auth0Tests/UserInfoSpec.swift | 2 +- Auth0Tests/UserPatchAttributesSpec.swift | 2 +- Auth0Tests/UsersSpec.swift | 135 ++- Auth0Tests/WebAuthErrorSpec.swift | 2 +- Auth0Tests/WebAuthSpec.swift | 127 +-- Auth0Tests/WebAuthSpies.swift | 4 +- Auth0Tests/WebAuthenticationSpec.swift | 2 +- Cartfile | 4 +- Cartfile.private | 5 +- Cartfile.resolved | 9 +- Gemfile.lock | 27 +- OAuth2Vision/ContentView.swift | 101 ++ OAuth2Vision/Info.plist | 15 + OAuth2Vision/OAuth2VisionApp.swift | 18 + Package.swift | 18 +- README.md | 10 +- 55 files changed, 2417 insertions(+), 1333 deletions(-) create mode 100644 Auth0.xcodeproj/xcshareddata/xcschemes/Auth0.visionOS.xcscheme create mode 100644 Auth0Tests/NetworkStub.swift create mode 100644 Auth0Tests/StubURLProtocol.swift create mode 100644 OAuth2Vision/ContentView.swift create mode 100644 OAuth2Vision/Info.plist create mode 100644 OAuth2Vision/OAuth2VisionApp.swift diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 87dde825..13eb903b 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -76,12 +76,12 @@ jobs: pod-lint: name: Lint podspec using Xcode ${{ matrix.xcode }} - runs-on: macos-13 + runs-on: macos-13-xlarge strategy: matrix: xcode: - - '15.0.1' + - '15.2' steps: - name: Checkout diff --git a/Auth0.podspec b/Auth0.podspec index 06753b58..e5dac922 100644 --- a/Auth0.podspec +++ b/Auth0.podspec @@ -33,7 +33,7 @@ Pod::Spec.new do |s| s.version = '2.8.0' s.summary = "Auth0 SDK for Apple platforms" s.description = <<-DESC - Auth0 SDK for iOS, macOS, tvOS, and watchOS apps. + Auth0 SDK for iOS, macOS, tvOS, watchOS and visionOS apps. DESC s.homepage = 'https://github.com/auth0/Auth0.swift' s.license = 'MIT' @@ -42,12 +42,12 @@ Pod::Spec.new do |s| s.social_media_url = 'https://twitter.com/auth0' s.source_files = 'Auth0/*.swift' s.resource_bundles = { s.name => 'Auth0/PrivacyInfo.xcprivacy' } - s.swift_versions = ['5.7', '5.8'] + s.swift_versions = ['5.9'] - s.dependency 'SimpleKeychain', '~> 1.1' - s.dependency 'JWTDecode', '~> 3.1' + s.dependency 'SimpleKeychain', '1.2.0-beta.0' + s.dependency 'JWTDecode', '3.2.0-beta.0' - s.ios.deployment_target = '13.0' + s.ios.deployment_target = '14.0' s.ios.exclude_files = macos_files s.ios.pod_target_xcconfig = { 'SWIFT_ACTIVE_COMPILATION_CONDITIONS' => 'WEB_AUTH_PLATFORM' } @@ -55,9 +55,13 @@ Pod::Spec.new do |s| s.osx.exclude_files = ios_files s.osx.pod_target_xcconfig = { 'SWIFT_ACTIVE_COMPILATION_CONDITIONS' => 'WEB_AUTH_PLATFORM' } - s.tvos.deployment_target = '13.0' + s.tvos.deployment_target = '14.0' s.tvos.exclude_files = excluded_files s.watchos.deployment_target = '7.0' s.watchos.exclude_files = excluded_files + + s.visionos.deployment_target = '1.0' + s.visionos.exclude_files = macos_files + s.visionos.pod_target_xcconfig = { 'SWIFT_ACTIVE_COMPILATION_CONDITIONS' => 'WEB_AUTH_PLATFORM' } end diff --git a/Auth0.xcodeproj/project.pbxproj b/Auth0.xcodeproj/project.pbxproj index a4fb965a..820084f6 100644 --- a/Auth0.xcodeproj/project.pbxproj +++ b/Auth0.xcodeproj/project.pbxproj @@ -128,21 +128,15 @@ 5CC9940424ED9EC90027DC74 /* CredentialsManagerError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B5E93F81EC45C22002A37F9 /* CredentialsManagerError.swift */; }; 5CD9FC6E26FE30A6009C2B27 /* Nimble.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CD9FC6B26FE30A6009C2B27 /* Nimble.xcframework */; }; 5CD9FC6F26FE30A6009C2B27 /* Quick.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CD9FC6C26FE30A6009C2B27 /* Quick.xcframework */; }; - 5CD9FC7026FE30A6009C2B27 /* OHHTTPStubs.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CD9FC6D26FE30A6009C2B27 /* OHHTTPStubs.xcframework */; }; 5CD9FC7226FE30BA009C2B27 /* Nimble.xcframework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 5CD9FC6B26FE30A6009C2B27 /* Nimble.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - 5CD9FC7326FE30BA009C2B27 /* OHHTTPStubs.xcframework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 5CD9FC6D26FE30A6009C2B27 /* OHHTTPStubs.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 5CD9FC7426FE30BA009C2B27 /* Quick.xcframework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 5CD9FC6C26FE30A6009C2B27 /* Quick.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 5CD9FC7526FE30C3009C2B27 /* Nimble.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CD9FC6B26FE30A6009C2B27 /* Nimble.xcframework */; }; - 5CD9FC7626FE30C3009C2B27 /* OHHTTPStubs.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CD9FC6D26FE30A6009C2B27 /* OHHTTPStubs.xcframework */; }; 5CD9FC7726FE30C3009C2B27 /* Quick.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CD9FC6C26FE30A6009C2B27 /* Quick.xcframework */; }; 5CD9FC7826FE30C8009C2B27 /* Nimble.xcframework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 5CD9FC6B26FE30A6009C2B27 /* Nimble.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - 5CD9FC7926FE30C8009C2B27 /* OHHTTPStubs.xcframework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 5CD9FC6D26FE30A6009C2B27 /* OHHTTPStubs.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 5CD9FC7A26FE30C8009C2B27 /* Quick.xcframework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 5CD9FC6C26FE30A6009C2B27 /* Quick.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 5CD9FC7B26FE30CF009C2B27 /* Nimble.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CD9FC6B26FE30A6009C2B27 /* Nimble.xcframework */; }; - 5CD9FC7C26FE30CF009C2B27 /* OHHTTPStubs.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CD9FC6D26FE30A6009C2B27 /* OHHTTPStubs.xcframework */; }; 5CD9FC7D26FE30CF009C2B27 /* Quick.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CD9FC6C26FE30A6009C2B27 /* Quick.xcframework */; }; 5CD9FC7E26FE30D4009C2B27 /* Nimble.xcframework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 5CD9FC6B26FE30A6009C2B27 /* Nimble.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - 5CD9FC7F26FE30D4009C2B27 /* OHHTTPStubs.xcframework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 5CD9FC6D26FE30A6009C2B27 /* OHHTTPStubs.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 5CD9FC8026FE30D4009C2B27 /* Quick.xcframework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 5CD9FC6C26FE30A6009C2B27 /* Quick.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 5CD9FC8626FE30EB009C2B27 /* JWTDecode.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CD9FC8426FE30EB009C2B27 /* JWTDecode.xcframework */; }; 5CD9FC8726FE30EB009C2B27 /* SimpleKeychain.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CD9FC8526FE30EB009C2B27 /* SimpleKeychain.xcframework */; }; @@ -327,6 +321,125 @@ A7DDDF6D2BC9A81E0077B067 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = A7DDDF6B2BC9A81E0077B067 /* PrivacyInfo.xcprivacy */; }; A7DDDF6E2BC9A81E0077B067 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = A7DDDF6B2BC9A81E0077B067 /* PrivacyInfo.xcprivacy */; }; A7DDDF702BC9A93F0077B067 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = A7DDDF6B2BC9A81E0077B067 /* PrivacyInfo.xcprivacy */; }; + C12BFE432C352DD400D1CC00 /* NetworkStub.swift in Sources */ = {isa = PBXBuildFile; fileRef = C177D76F2C2BDFE40094C657 /* NetworkStub.swift */; }; + C12BFE442C352DD700D1CC00 /* StubURLProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C177D7742C2BE00D0094C657 /* StubURLProtocol.swift */; }; + C177D6C32C2ADDEB0094C657 /* Auth0.plist in Resources */ = {isa = PBXBuildFile; fileRef = C177D6C22C2ADDEB0094C657 /* Auth0.plist */; }; + C177D6C72C2ADEB60094C657 /* CwlPreconditionTesting in Frameworks */ = {isa = PBXBuildFile; productRef = C177D6C62C2ADEB60094C657 /* CwlPreconditionTesting */; }; + C177D6D42C2B0DCB0094C657 /* CwlPreconditionTesting in Frameworks */ = {isa = PBXBuildFile; productRef = C177D6D32C2B0DCB0094C657 /* CwlPreconditionTesting */; }; + C177D7702C2BDFE40094C657 /* NetworkStub.swift in Sources */ = {isa = PBXBuildFile; fileRef = C177D76F2C2BDFE40094C657 /* NetworkStub.swift */; }; + C177D7712C2BDFE40094C657 /* NetworkStub.swift in Sources */ = {isa = PBXBuildFile; fileRef = C177D76F2C2BDFE40094C657 /* NetworkStub.swift */; }; + C177D7722C2BDFE40094C657 /* NetworkStub.swift in Sources */ = {isa = PBXBuildFile; fileRef = C177D76F2C2BDFE40094C657 /* NetworkStub.swift */; }; + C177D7752C2BE00D0094C657 /* StubURLProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C177D7742C2BE00D0094C657 /* StubURLProtocol.swift */; }; + C177D7762C2BE00D0094C657 /* StubURLProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C177D7742C2BE00D0094C657 /* StubURLProtocol.swift */; }; + C177D7772C2BE00D0094C657 /* StubURLProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C177D7742C2BE00D0094C657 /* StubURLProtocol.swift */; }; + C19413DC2C2D792200FE88F7 /* CwlPreconditionTesting in Frameworks */ = {isa = PBXBuildFile; productRef = C19413DB2C2D792200FE88F7 /* CwlPreconditionTesting */; }; + C196EB072C35D0C700D108AA /* CwlPreconditionTesting in Frameworks */ = {isa = PBXBuildFile; productRef = C196EB062C35D0C700D108AA /* CwlPreconditionTesting */; }; + C1B3B9AF2C24B297004A32A4 /* OAuth2VisionApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1B3B9AE2C24B297004A32A4 /* OAuth2VisionApp.swift */; }; + C1B3B9B12C24B297004A32A4 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1B3B9B02C24B297004A32A4 /* ContentView.swift */; }; + C1B3B9D32C24B39E004A32A4 /* Auth0.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C1B3B9C02C24B39E004A32A4 /* Auth0.framework */; }; + C1B3B9D42C24B39E004A32A4 /* Auth0.framework in Copy Files */ = {isa = PBXBuildFile; fileRef = C1B3B9C02C24B39E004A32A4 /* Auth0.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + C1B3B9DF2C24B599004A32A4 /* JWTDecode.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CD9FC8426FE30EB009C2B27 /* JWTDecode.xcframework */; }; + C1B3B9E02C24B599004A32A4 /* JWTDecode.xcframework in Copy Files */ = {isa = PBXBuildFile; fileRef = 5CD9FC8426FE30EB009C2B27 /* JWTDecode.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + C1B3B9E32C24B5B5004A32A4 /* SimpleKeychain.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CD9FC8526FE30EB009C2B27 /* SimpleKeychain.xcframework */; }; + C1B3B9E42C24B5B5004A32A4 /* SimpleKeychain.xcframework in Copy Files */ = {isa = PBXBuildFile; fileRef = 5CD9FC8526FE30EB009C2B27 /* SimpleKeychain.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + C1B3B9E52C24B65B004A32A4 /* Nimble.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CD9FC6B26FE30A6009C2B27 /* Nimble.xcframework */; }; + C1B3B9E62C24B65B004A32A4 /* Nimble.xcframework in Copy Files */ = {isa = PBXBuildFile; fileRef = 5CD9FC6B26FE30A6009C2B27 /* Nimble.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + C1B3B9EA2C24B65D004A32A4 /* Quick.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CD9FC6C26FE30A6009C2B27 /* Quick.xcframework */; }; + C1B3B9EB2C24B65D004A32A4 /* Quick.xcframework in Copy Files */ = {isa = PBXBuildFile; fileRef = 5CD9FC6C26FE30A6009C2B27 /* Quick.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + C1B3B9EC2C24B692004A32A4 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = A7DDDF6B2BC9A81E0077B067 /* PrivacyInfo.xcprivacy */; }; + C1B3B9EE2C24B6D4004A32A4 /* Auth0Authentication.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FDE87491D8A424700EA27DC /* Auth0Authentication.swift */; }; + C1B3B9EF2C24B6D4004A32A4 /* Authentication.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FDE874A1D8A424700EA27DC /* Authentication.swift */; }; + C1B3B9F02C24B6D4004A32A4 /* AuthenticationError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FDE874B1D8A424700EA27DC /* AuthenticationError.swift */; }; + C1B3B9F12C24B6D4004A32A4 /* PasswordlessType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C354C03276CE1A500ADBC86 /* PasswordlessType.swift */; }; + C1B3B9F22C24B6D4004A32A4 /* Challenge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 970BC36A25C27095007A7745 /* Challenge.swift */; }; + C1B3B9F32C24B6D4004A32A4 /* JWKS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C4F552223C8FBA100C89615 /* JWKS.swift */; }; + C1B3B9F42C24B6D4004A32A4 /* UserInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B2860CD1EEAC30500C75D54 /* UserInfo.swift */; }; + C1B3B9F52C24B6D4004A32A4 /* Credentials.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FDE874E1D8A424700EA27DC /* Credentials.swift */; }; + C1B3B9F62C24B6D4004A32A4 /* Handlers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FDE874F1D8A424700EA27DC /* Handlers.swift */; }; + C1B3B9F72C24B6D4004A32A4 /* Telemetry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FDE87461D8A422300EA27DC /* Telemetry.swift */; }; + C1B3B9F82C24B6D4004A32A4 /* IDTokenValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB41D3E23D0BA2C00074024 /* IDTokenValidator.swift */; }; + C1B3B9F92C24B6D4004A32A4 /* IDTokenValidatorContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB41D3C23D0BA2C00074024 /* IDTokenValidatorContext.swift */; }; + C1B3B9FA2C24B6D4004A32A4 /* IDTokenSignatureValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB41D3D23D0BA2C00074024 /* IDTokenSignatureValidator.swift */; }; + C1B3B9FB2C24B6D4004A32A4 /* ClaimValidators.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB41D7023D0BED200074024 /* ClaimValidators.swift */; }; + C1B3B9FC2C24B6D4004A32A4 /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B1748731EF2D3A40060E653 /* Helpers.swift */; }; + C1B3B9FD2C24B6D4004A32A4 /* BioAuthentication.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B9262BF1ECF0CA800F4F6D3 /* BioAuthentication.swift */; }; + C1B3B9FE2C24B6D4004A32A4 /* CredentialsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BEDE1891EC21B040007300D /* CredentialsManager.swift */; }; + C1B3B9FF2C24B6D4004A32A4 /* CredentialsStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C80980A275A7B8600DC0A76 /* CredentialsStorage.swift */; }; + C1B3BA002C24B6D4004A32A4 /* CredentialsManagerError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B5E93F81EC45C22002A37F9 /* CredentialsManagerError.swift */; }; + C1B3BA012C24B6D4004A32A4 /* Array+Encode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C4F551923C8FB8E00C89615 /* Array+Encode.swift */; }; + C1B3BA022C24B6D4004A32A4 /* String+URLSafe.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C4F551823C8FB8E00C89615 /* String+URLSafe.swift */; }; + C1B3BA032C24B6D4004A32A4 /* NSData+URLSafe.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FCAB1721D09009600331C84 /* NSData+URLSafe.swift */; }; + C1B3BA042C24B6D4004A32A4 /* NSURL+Auth0.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FCAB1781D09124D00331C84 /* NSURL+Auth0.swift */; }; + C1B3BA052C24B6D4004A32A4 /* NSURLComponents+OAuth2.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FCAB1701D09005A00331C84 /* NSURLComponents+OAuth2.swift */; }; + C1B3BA062C24B6D4004A32A4 /* JWT+Header.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB41D6B23D0BBA500074024 /* JWT+Header.swift */; }; + C1B3BA072C24B6D4004A32A4 /* JWK+RSA.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C49EB3423EB5A80008D562F /* JWK+RSA.swift */; }; + C1B3BA082C24B6D4004A32A4 /* Optional+DebugDescription.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB41D3F23D0BA2C00074024 /* Optional+DebugDescription.swift */; }; + C1B3BA092C24B6D4004A32A4 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F6FAC621D09E98000D5B4EA /* Logger.swift */; }; + C1B3BA0A2C24B6D4004A32A4 /* Loggable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F28B4601D8216180000EB23 /* Loggable.swift */; }; + C1B3BA0B2C24B6D4004A32A4 /* Management.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FF465BB1CE2AC4500F7ED8C /* Management.swift */; }; + C1B3BA0C2C24B6D4004A32A4 /* UserPatchAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FADB60B1CED7E0800D4BB50 /* UserPatchAttributes.swift */; }; + C1B3BA0D2C24B6D4004A32A4 /* Users.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FADB6051CED27FB00D4BB50 /* Users.swift */; }; + C1B3BA0E2C24B6D4004A32A4 /* ManagementError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FD255B31D14DD2600387ECB /* ManagementError.swift */; }; + C1B3BA0F2C24B6D4004A32A4 /* JSONObjectPayload.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F74CB3F1CEFD5E600226823 /* JSONObjectPayload.swift */; }; + C1B3BA102C24B6D4004A32A4 /* Request.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FE2F8B71CD0E910003628F4 /* Request.swift */; }; + C1B3BA112C24B6D4004A32A4 /* Requestable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FE2F8B11CCEAED8003628F4 /* Requestable.swift */; }; + C1B3BA122C24B6D4004A32A4 /* Response.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FE2F8BA1CD0EAAD003628F4 /* Response.swift */; }; + C1B3BA132C24B6D4004A32A4 /* JWTAlgorithm.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C4F550423C8FADE00C89615 /* JWTAlgorithm.swift */; }; + C1B3BA142C24B6D4004A32A4 /* ChallengeGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5E9E316273ACCA5000CDB0A /* ChallengeGenerator.swift */; }; + C1B3BA152C24B6D4004A32A4 /* ASProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B16D88C1F7141A0009476A5 /* ASProvider.swift */; }; + C1B3BA172C24B6D4004A32A4 /* LoginTransaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C41F6A3244DC94E00252548 /* LoginTransaction.swift */; }; + C1B3BA182C24B6D4004A32A4 /* ClearSessionTransaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C41F6A9244DCAFB00252548 /* ClearSessionTransaction.swift */; }; + C1B3BA192C24B6D4004A32A4 /* MobileWebAuth.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C41F6B0244DCC3B00252548 /* MobileWebAuth.swift */; }; + C1B3BA1B2C24B6D4004A32A4 /* AuthTransaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F53F5CD1CFD157300476A46 /* AuthTransaction.swift */; }; + C1B3BA1C2C24B6D4004A32A4 /* WebAuthUserAgent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B16D8921F714324009476A5 /* WebAuthUserAgent.swift */; }; + C1B3BA1D2C24B6D4004A32A4 /* OAuth2Grant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F4A1F951D00AABC00C72242 /* OAuth2Grant.swift */; }; + C1B3BA1E2C24B6D4004A32A4 /* TransactionStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FCAB1751D0900CF00331C84 /* TransactionStore.swift */; }; + C1B3BA1F2C24B6D4004A32A4 /* WebAuth.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F3965C11CF67CF000CDE7C0 /* WebAuth.swift */; }; + C1B3BA202C24B6D4004A32A4 /* Auth0WebAuth.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FAE9C901D8878D400A871CE /* Auth0WebAuth.swift */; }; + C1B3BA212C24B6D4004A32A4 /* WebAuthError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FD255B91D14F70B00387ECB /* WebAuthError.swift */; }; + C1B3BA222C24B6D4004A32A4 /* WebAuthentication.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C0AF09C2833420200162044 /* WebAuthentication.swift */; }; + C1B3BA232C24B6D4004A32A4 /* Auth0.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F06DDC81CC66B710011842B /* Auth0.swift */; }; + C1B3BA242C24B6D4004A32A4 /* Auth0Error.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FD255B61D14F00900387ECB /* Auth0Error.swift */; }; + C1B3BA252C24B6D4004A32A4 /* Version.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C6513A62791CDDE004EBC22 /* Version.swift */; }; + C1B3BA262C24B6F5004A32A4 /* Documentation.docc in Sources */ = {isa = PBXBuildFile; fileRef = 5CA541CC2B1A81A700E4284D /* Documentation.docc */; }; + C1B3BA2A2C24B971004A32A4 /* Auth0.plist in Resources */ = {isa = PBXBuildFile; fileRef = 5FE686A01D1877C10075874C /* Auth0.plist */; }; + C1B3BA2B2C24BA36004A32A4 /* LoggerSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F28B4661D8300D50000EB23 /* LoggerSpec.swift */; }; + C1B3BA2C2C24BA36004A32A4 /* TelemetrySpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FE686A91D1894AA0075874C /* TelemetrySpec.swift */; }; + C1B3BA2D2C24BA36004A32A4 /* AuthenticationSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FBBF0421CCA90300024D2AF /* AuthenticationSpec.swift */; }; + C1B3BA2E2C24BA36004A32A4 /* CredentialsSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FE2F8A51CCA9C17003628F4 /* CredentialsSpec.swift */; }; + C1B3BA2F2C24BA36004A32A4 /* UserInfoSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B2860D41EEF20F300C75D54 /* UserInfoSpec.swift */; }; + C1B3BA302C24BA36004A32A4 /* JWKSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C4F553423C9124200C89615 /* JWKSpec.swift */; }; + C1B3BA312C24BA36004A32A4 /* AuthenticationErrorSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FD255B01D14A9E000387ECB /* AuthenticationErrorSpec.swift */; }; + C1B3BA322C24BA36004A32A4 /* ManagementSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FADB6081CED500900D4BB50 /* ManagementSpec.swift */; }; + C1B3BA332C24BA36004A32A4 /* ManagementErrorSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C809D99275FA3EF00F15A67 /* ManagementErrorSpec.swift */; }; + C1B3BA342C24BA36004A32A4 /* UserPatchAttributesSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FADB60E1CED7E5200D4BB50 /* UserPatchAttributesSpec.swift */; }; + C1B3BA352C24BA36004A32A4 /* UsersSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FADB6021CEC0C3300D4BB50 /* UsersSpec.swift */; }; + C1B3BA362C24BA36004A32A4 /* ResponseSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F1FBB981D8A4465006B0B85 /* ResponseSpec.swift */; }; + C1B3BA372C24BA36004A32A4 /* RequestSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = D581CF762757D773007327D1 /* RequestSpec.swift */; }; + C1B3BA382C24BA36004A32A4 /* JWTAlgorithmSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C4F553923C9125600C89615 /* JWTAlgorithmSpec.swift */; }; + C1B3BA392C24BA36004A32A4 /* LoginTransactionSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C41F6B4244DCEED00252548 /* LoginTransactionSpec.swift */; }; + C1B3BA3A2C24BA36004A32A4 /* ClearSessionTransactionSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CF539272836FB0C0073F623 /* ClearSessionTransactionSpec.swift */; }; + C1B3BA3B2C24BA36004A32A4 /* ASProviderSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CF5392A283835460073F623 /* ASProviderSpec.swift */; }; + C1B3BA3D2C24BA36004A32A4 /* WebAuthErrorSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FA250531D4A85A200C544FA /* WebAuthErrorSpec.swift */; }; + C1B3BA3E2C24BA36004A32A4 /* ChallengeGeneratorSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F09C6A61D07532B00727E55 /* ChallengeGeneratorSpec.swift */; }; + C1B3BA3F2C24BA36004A32A4 /* OAuth2GrantSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F4A1F971D00AEDF00C72242 /* OAuth2GrantSpec.swift */; }; + C1B3BA402C24BA36004A32A4 /* WebAuthSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F53F5D61CFFAA4A00476A46 /* WebAuthSpec.swift */; }; + C1B3BA412C24BA36004A32A4 /* TransactionStoreSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F686A761D4AB90900412E3D /* TransactionStoreSpec.swift */; }; + C1B3BA422C24BA36004A32A4 /* WebAuthenticationSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CF5391C2836CEC00073F623 /* WebAuthenticationSpec.swift */; }; + C1B3BA432C24BA36004A32A4 /* WebAuthSpies.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CF5391F2836D9720073F623 /* WebAuthSpies.swift */; }; + C1B3BA442C24BA36004A32A4 /* IDTokenValidatorBaseSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB41D7523D0C15000074024 /* IDTokenValidatorBaseSpec.swift */; }; + C1B3BA452C24BA36004A32A4 /* IDTokenValidatorMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB41D5323D0BA4B00074024 /* IDTokenValidatorMocks.swift */; }; + C1B3BA462C24BA36004A32A4 /* IDTokenValidatorSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB41D5223D0BA4B00074024 /* IDTokenValidatorSpec.swift */; }; + C1B3BA472C24BA36004A32A4 /* IDTokenSignatureValidatorSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB41D5123D0BA4B00074024 /* IDTokenSignatureValidatorSpec.swift */; }; + C1B3BA482C24BA36004A32A4 /* ClaimValidatorsSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB41D8123D611AE00074024 /* ClaimValidatorsSpec.swift */; }; + C1B3BA492C24BA37004A32A4 /* BioAuthenticationSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B9262C11ECF0CBA00F4F6D3 /* BioAuthenticationSpec.swift */; }; + C1B3BA4A2C24BA37004A32A4 /* CredentialsManagerSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BEDE1931EC3331A0007300D /* CredentialsManagerSpec.swift */; }; + C1B3BA4B2C24BA37004A32A4 /* CredentialsManagerErrorSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C809D95275F878E00F15A67 /* CredentialsManagerErrorSpec.swift */; }; + C1B3BA4C2C24BA37004A32A4 /* CryptoExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C4F552D23C9123000C89615 /* CryptoExtensions.swift */; }; + C1B3BA4D2C24BA37004A32A4 /* Matchers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FBBF0371CC964BC0024D2AF /* Matchers.swift */; }; + C1B3BA4E2C24BA37004A32A4 /* Responses.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FBBF03A1CC96AA70024D2AF /* Responses.swift */; }; + C1B3BA4F2C24BA37004A32A4 /* Generators.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C4F552C23C9123000C89615 /* Generators.swift */; }; + C1B3BA502C24BA37004A32A4 /* Auth0Spec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F93BC0A1CC6B0DE0031519F /* Auth0Spec.swift */; }; D581CF772757D773007327D1 /* RequestSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = D581CF762757D773007327D1 /* RequestSpec.swift */; }; D581CF782757D773007327D1 /* RequestSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = D581CF762757D773007327D1 /* RequestSpec.swift */; }; D581CF792757D773007327D1 /* RequestSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = D581CF762757D773007327D1 /* RequestSpec.swift */; }; @@ -377,6 +490,20 @@ remoteGlobalIDString = 5F3965C61CF67DD800CDE7C0; remoteInfo = OAuth2; }; + C1B3B9CB2C24B39E004A32A4 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 5F049B601CB42C29006F6C05 /* Project object */; + proxyType = 1; + remoteGlobalIDString = C1B3B9A72C24B297004A32A4; + remoteInfo = OAuth2Vision; + }; + C1B3BA282C24B95A004A32A4 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 5F049B601CB42C29006F6C05 /* Project object */; + proxyType = 1; + remoteGlobalIDString = C1B3B9BF2C24B39E004A32A4; + remoteInfo = Auth0.visionOS; + }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -426,7 +553,6 @@ dstSubfolderSpec = 10; files = ( 5CD9FC7226FE30BA009C2B27 /* Nimble.xcframework in CopyFiles */, - 5CD9FC7326FE30BA009C2B27 /* OHHTTPStubs.xcframework in CopyFiles */, 5CD9FC7426FE30BA009C2B27 /* Quick.xcframework in CopyFiles */, ); runOnlyForDeploymentPostprocessing = 0; @@ -438,7 +564,6 @@ dstSubfolderSpec = 10; files = ( 5CD9FC7826FE30C8009C2B27 /* Nimble.xcframework in CopyFiles */, - 5CD9FC7926FE30C8009C2B27 /* OHHTTPStubs.xcframework in CopyFiles */, 5CD9FC7A26FE30C8009C2B27 /* Quick.xcframework in CopyFiles */, ); runOnlyForDeploymentPostprocessing = 0; @@ -450,11 +575,35 @@ dstSubfolderSpec = 10; files = ( 5CD9FC7E26FE30D4009C2B27 /* Nimble.xcframework in CopyFiles */, - 5CD9FC7F26FE30D4009C2B27 /* OHHTTPStubs.xcframework in CopyFiles */, 5CD9FC8026FE30D4009C2B27 /* Quick.xcframework in CopyFiles */, ); runOnlyForDeploymentPostprocessing = 0; }; + C1B3B9D82C24B39E004A32A4 /* Copy Files */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + C1B3B9D42C24B39E004A32A4 /* Auth0.framework in Copy Files */, + C1B3B9E02C24B599004A32A4 /* JWTDecode.xcframework in Copy Files */, + C1B3B9E42C24B5B5004A32A4 /* SimpleKeychain.xcframework in Copy Files */, + ); + name = "Copy Files"; + runOnlyForDeploymentPostprocessing = 0; + }; + C1B3B9E72C24B65B004A32A4 /* Copy Files */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + C1B3B9EB2C24B65D004A32A4 /* Quick.xcframework in Copy Files */, + C1B3B9E62C24B65B004A32A4 /* Nimble.xcframework in Copy Files */, + ); + name = "Copy Files"; + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ @@ -478,7 +627,6 @@ 5B7EE48420FCA0A200367724 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 5B9262BF1ECF0CA800F4F6D3 /* BioAuthentication.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BioAuthentication.swift; sourceTree = ""; }; 5B9262C11ECF0CBA00F4F6D3 /* BioAuthenticationSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = BioAuthenticationSpec.swift; path = Auth0Tests/BioAuthenticationSpec.swift; sourceTree = SOURCE_ROOT; }; - 5B9A54411E49E3AE004B5454 /* Auth0.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Auth0.plist; sourceTree = SOURCE_ROOT; }; 5BA58D33209081A700782DD1 /* Cartfile */ = {isa = PBXFileReference; lastKnownFileType = text; path = Cartfile; sourceTree = ""; }; 5BEDE1891EC21B040007300D /* CredentialsManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CredentialsManager.swift; sourceTree = ""; }; 5BEDE1931EC3331A0007300D /* CredentialsManagerSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; name = CredentialsManagerSpec.swift; path = Auth0Tests/CredentialsManagerSpec.swift; sourceTree = SOURCE_ROOT; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; @@ -519,7 +667,6 @@ 5CB41D8123D611AE00074024 /* ClaimValidatorsSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClaimValidatorsSpec.swift; sourceTree = ""; }; 5CD9FC6B26FE30A6009C2B27 /* Nimble.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = Nimble.xcframework; path = Carthage/Build/Nimble.xcframework; sourceTree = ""; }; 5CD9FC6C26FE30A6009C2B27 /* Quick.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = Quick.xcframework; path = Carthage/Build/Quick.xcframework; sourceTree = ""; }; - 5CD9FC6D26FE30A6009C2B27 /* OHHTTPStubs.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = OHHTTPStubs.xcframework; path = Carthage/Build/OHHTTPStubs.xcframework; sourceTree = ""; }; 5CD9FC8426FE30EB009C2B27 /* JWTDecode.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = JWTDecode.xcframework; path = Carthage/Build/JWTDecode.xcframework; sourceTree = ""; }; 5CD9FC8526FE30EB009C2B27 /* SimpleKeychain.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = SimpleKeychain.xcframework; path = Carthage/Build/SimpleKeychain.xcframework; sourceTree = ""; }; 5CF5391C2836CEC00073F623 /* WebAuthenticationSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebAuthenticationSpec.swift; sourceTree = ""; }; @@ -599,6 +746,15 @@ 5FF465BB1CE2AC4500F7ED8C /* Management.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Management.swift; path = Auth0/Management.swift; sourceTree = SOURCE_ROOT; }; 970BC36A25C27095007A7745 /* Challenge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Challenge.swift; sourceTree = ""; }; A7DDDF6B2BC9A81E0077B067 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; + C177D6C22C2ADDEB0094C657 /* Auth0.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Auth0.plist; sourceTree = ""; }; + C177D76F2C2BDFE40094C657 /* NetworkStub.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkStub.swift; sourceTree = ""; }; + C177D7742C2BE00D0094C657 /* StubURLProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StubURLProtocol.swift; sourceTree = ""; }; + C1B3B9A82C24B297004A32A4 /* OAuth2Vision.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = OAuth2Vision.app; sourceTree = BUILT_PRODUCTS_DIR; }; + C1B3B9AE2C24B297004A32A4 /* OAuth2VisionApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OAuth2VisionApp.swift; sourceTree = ""; }; + C1B3B9B02C24B297004A32A4 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; + C1B3B9B72C24B298004A32A4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + C1B3B9C02C24B39E004A32A4 /* Auth0.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Auth0.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + C1B3B9C72C24B39E004A32A4 /* Auth0Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Auth0Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; D581CF762757D773007327D1 /* RequestSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestSpec.swift; sourceTree = ""; }; D5E9E316273ACCA5000CDB0A /* ChallengeGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChallengeGenerator.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -642,8 +798,8 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + C177D6C72C2ADEB60094C657 /* CwlPreconditionTesting in Frameworks */, 5CD9FC6E26FE30A6009C2B27 /* Nimble.xcframework in Frameworks */, - 5CD9FC7026FE30A6009C2B27 /* OHHTTPStubs.xcframework in Frameworks */, 5CD9FC6F26FE30A6009C2B27 /* Quick.xcframework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -652,8 +808,8 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + C177D6D42C2B0DCB0094C657 /* CwlPreconditionTesting in Frameworks */, 5CD9FC7526FE30C3009C2B27 /* Nimble.xcframework in Frameworks */, - 5CD9FC7626FE30C3009C2B27 /* OHHTTPStubs.xcframework in Frameworks */, 5CD9FC7726FE30C3009C2B27 /* Quick.xcframework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -676,8 +832,8 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + C196EB072C35D0C700D108AA /* CwlPreconditionTesting in Frameworks */, 5CD9FC7B26FE30CF009C2B27 /* Nimble.xcframework in Frameworks */, - 5CD9FC7C26FE30CF009C2B27 /* OHHTTPStubs.xcframework in Frameworks */, 5CD9FC7D26FE30CF009C2B27 /* Quick.xcframework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -692,6 +848,33 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + C1B3B9A52C24B297004A32A4 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + C1B3B9D32C24B39E004A32A4 /* Auth0.framework in Frameworks */, + C1B3B9DF2C24B599004A32A4 /* JWTDecode.xcframework in Frameworks */, + C1B3B9E32C24B5B5004A32A4 /* SimpleKeychain.xcframework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + C1B3B9BD2C24B39E004A32A4 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + C1B3B9C42C24B39E004A32A4 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + C1B3B9EA2C24B65D004A32A4 /* Quick.xcframework in Frameworks */, + C1B3B9E52C24B65B004A32A4 /* Nimble.xcframework in Frameworks */, + C19413DC2C2D792200FE88F7 /* CwlPreconditionTesting in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ @@ -818,6 +1001,7 @@ 5F3965C81CF67DD800CDE7C0 /* App */, 5B7EE45920FC9F3200367724 /* OAuth2TV */, 5B7EE47A20FCA0A100367724 /* OAuth2Mac */, + C1B3B9A92C24B297004A32A4 /* OAuth2Vision */, 5F06DDC21CC5712F0011842B /* Frameworks */, 5F049B6A1CB42C29006F6C05 /* Products */, 5FAE9C861D8872E900A871CE /* Supporting Files */, @@ -837,6 +1021,9 @@ 5F331AF91D4BB24C00AE4382 /* Auth0Tests.xctest */, 5B7EE45820FC9F3200367724 /* OAuth2TV.app */, 5B7EE47920FCA0A100367724 /* OAuth2Mac.app */, + C1B3B9A82C24B297004A32A4 /* OAuth2Vision.app */, + C1B3B9C02C24B39E004A32A4 /* Auth0.framework */, + C1B3B9C72C24B39E004A32A4 /* Auth0Tests.xctest */, ); name = Products; sourceTree = ""; @@ -864,6 +1051,7 @@ 5F06DD921CC451430011842B /* Auth0Tests */ = { isa = PBXGroup; children = ( + C177D76E2C2BDF9D0094C657 /* StubNetworking */, 5F28B4651D8300BB0000EB23 /* Logger */, 5FE686A81D1894990075874C /* Telemetry */, 5FBBF0411CCA901B0024D2AF /* Authentication */, @@ -885,7 +1073,6 @@ 5CD9FC8426FE30EB009C2B27 /* JWTDecode.xcframework */, 5CD9FC8526FE30EB009C2B27 /* SimpleKeychain.xcframework */, 5CD9FC6B26FE30A6009C2B27 /* Nimble.xcframework */, - 5CD9FC6D26FE30A6009C2B27 /* OHHTTPStubs.xcframework */, 5CD9FC6C26FE30A6009C2B27 /* Quick.xcframework */, ); name = Frameworks; @@ -925,8 +1112,8 @@ 5F3965D01CF67DD800CDE7C0 /* Assets.xcassets */, 5F3965D21CF67DD800CDE7C0 /* LaunchScreen.storyboard */, 5F3965CD1CF67DD800CDE7C0 /* Main.storyboard */, + C177D6C22C2ADDEB0094C657 /* Auth0.plist */, 5F3965D51CF67DD800CDE7C0 /* Info.plist */, - 5B9A54411E49E3AE004B5454 /* Auth0.plist */, ); path = App; sourceTree = ""; @@ -1100,6 +1287,25 @@ name = Management; sourceTree = ""; }; + C177D76E2C2BDF9D0094C657 /* StubNetworking */ = { + isa = PBXGroup; + children = ( + C177D76F2C2BDFE40094C657 /* NetworkStub.swift */, + C177D7742C2BE00D0094C657 /* StubURLProtocol.swift */, + ); + name = StubNetworking; + sourceTree = ""; + }; + C1B3B9A92C24B297004A32A4 /* OAuth2Vision */ = { + isa = PBXGroup; + children = ( + C1B3B9AE2C24B297004A32A4 /* OAuth2VisionApp.swift */, + C1B3B9B02C24B297004A32A4 /* ContentView.swift */, + C1B3B9B72C24B298004A32A4 /* Info.plist */, + ); + path = OAuth2Vision; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ @@ -1131,6 +1337,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + C1B3B9BB2C24B39E004A32A4 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXHeadersBuildPhase section */ /* Begin PBXNativeTarget section */ @@ -1226,6 +1439,9 @@ 5FCAB1651D07ABC000331C84 /* PBXTargetDependency */, ); name = Auth0Tests.iOS; + packageProductDependencies = ( + C177D6C62C2ADEB60094C657 /* CwlPreconditionTesting */, + ); productName = Auth0Tests.iOS; productReference = 5F06DDA01CC451540011842B /* Auth0Tests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; @@ -1246,6 +1462,9 @@ 5B7EE48A20FCA0A600367724 /* PBXTargetDependency */, ); name = Auth0Tests.macOS; + packageProductDependencies = ( + C177D6D32C2B0DCB0094C657 /* CwlPreconditionTesting */, + ); productName = Auth0Tests.OSX; productReference = 5F06DDAF1CC451700011842B /* Auth0Tests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; @@ -1304,6 +1523,9 @@ 5B7EE47120FC9FFE00367724 /* PBXTargetDependency */, ); name = Auth0Tests.tvOS; + packageProductDependencies = ( + C196EB062C35D0C700D108AA /* CwlPreconditionTesting */, + ); productName = Auth0Tests.tvOS; productReference = 5F331AF91D4BB24C00AE4382 /* Auth0Tests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; @@ -1327,13 +1549,76 @@ productReference = 5F3965C71CF67DD800CDE7C0 /* OAuth2.app */; productType = "com.apple.product-type.application"; }; + C1B3B9A72C24B297004A32A4 /* OAuth2Vision */ = { + isa = PBXNativeTarget; + buildConfigurationList = C1B3B9BA2C24B298004A32A4 /* Build configuration list for PBXNativeTarget "OAuth2Vision" */; + buildPhases = ( + C1B3B9A42C24B297004A32A4 /* Sources */, + C1B3B9A52C24B297004A32A4 /* Frameworks */, + C1B3B9A62C24B297004A32A4 /* Resources */, + C1B3B9D82C24B39E004A32A4 /* Copy Files */, + C1B3BA272C24B864004A32A4 /* Auth0 */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = OAuth2Vision; + packageProductDependencies = ( + ); + productName = OAuth2Vision; + productReference = C1B3B9A82C24B297004A32A4 /* OAuth2Vision.app */; + productType = "com.apple.product-type.application"; + }; + C1B3B9BF2C24B39E004A32A4 /* Auth0.visionOS */ = { + isa = PBXNativeTarget; + buildConfigurationList = C1B3B9D52C24B39E004A32A4 /* Build configuration list for PBXNativeTarget "Auth0.visionOS" */; + buildPhases = ( + C1B3B9BB2C24B39E004A32A4 /* Headers */, + C1B3B9BC2C24B39E004A32A4 /* Sources */, + C1B3B9BD2C24B39E004A32A4 /* Frameworks */, + C1B3B9BE2C24B39E004A32A4 /* Resources */, + C1B3B9ED2C24B699004A32A4 /* SwiftLint */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Auth0.visionOS; + productName = Auth0.visionOS; + productReference = C1B3B9C02C24B39E004A32A4 /* Auth0.framework */; + productType = "com.apple.product-type.framework"; + }; + C1B3B9C62C24B39E004A32A4 /* Auth0Tests.visionOS */ = { + isa = PBXNativeTarget; + buildConfigurationList = C1B3B9D92C24B39E004A32A4 /* Build configuration list for PBXNativeTarget "Auth0Tests.visionOS" */; + buildPhases = ( + C1B3B9C32C24B39E004A32A4 /* Sources */, + C1B3B9C42C24B39E004A32A4 /* Frameworks */, + C1B3B9C52C24B39E004A32A4 /* Resources */, + C1B3B9E72C24B65B004A32A4 /* Copy Files */, + ); + buildRules = ( + ); + dependencies = ( + C1B3BA292C24B95A004A32A4 /* PBXTargetDependency */, + C1B3B9CC2C24B39E004A32A4 /* PBXTargetDependency */, + ); + name = Auth0Tests.visionOS; + packageProductDependencies = ( + C19413DB2C2D792200FE88F7 /* CwlPreconditionTesting */, + ); + productName = Auth0Tests.visionOS; + productReference = C1B3B9C72C24B39E004A32A4 /* Auth0Tests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 5F049B601CB42C29006F6C05 /* Project object */ = { isa = PBXProject; attributes = { - LastSwiftUpdateCheck = 0940; + LastSwiftUpdateCheck = 1540; LastUpgradeCheck = 1400; ORGANIZATIONNAME = Auth0; TargetAttributes = { @@ -1387,6 +1672,16 @@ LastSwiftMigration = 1020; ProvisioningStyle = Manual; }; + C1B3B9A72C24B297004A32A4 = { + CreatedOnToolsVersion = 15.4; + }; + C1B3B9BF2C24B39E004A32A4 = { + CreatedOnToolsVersion = 15.4; + }; + C1B3B9C62C24B39E004A32A4 = { + CreatedOnToolsVersion = 15.4; + TestTargetID = C1B3B9A72C24B297004A32A4; + }; }; }; buildConfigurationList = 5F049B631CB42C29006F6C05 /* Build configuration list for PBXProject "Auth0" */; @@ -1398,6 +1693,9 @@ Base, ); mainGroup = 5F049B5F1CB42C29006F6C05; + packageReferences = ( + C177D6C52C2ADEB60094C657 /* XCRemoteSwiftPackageReference "CwlPreconditionTesting" */, + ); productRefGroup = 5F049B6A1CB42C29006F6C05 /* Products */; projectDirPath = ""; projectRoot = ""; @@ -1406,12 +1704,15 @@ 5F06DD841CC448C90011842B /* Auth0.macOS */, 5F23E6F51D4B87F000C3F2D9 /* Auth0.tvOS */, 5F23E6B81D4ACA7100C3F2D9 /* Auth0.watchOS */, + C1B3B9BF2C24B39E004A32A4 /* Auth0.visionOS */, 5F06DD9F1CC451540011842B /* Auth0Tests.iOS */, 5F06DDAE1CC451700011842B /* Auth0Tests.macOS */, 5F331AF81D4BB24C00AE4382 /* Auth0Tests.tvOS */, + C1B3B9C62C24B39E004A32A4 /* Auth0Tests.visionOS */, 5F3965C61CF67DD800CDE7C0 /* OAuth2 */, 5B7EE45720FC9F3200367724 /* OAuth2TV */, 5B7EE47820FCA0A100367724 /* OAuth2Mac */, + C1B3B9A72C24B297004A32A4 /* OAuth2Vision */, ); }; /* End PBXProject section */ @@ -1501,6 +1802,30 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + C1B3B9A62C24B297004A32A4 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + C177D6C32C2ADDEB0094C657 /* Auth0.plist in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + C1B3B9BE2C24B39E004A32A4 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + C1B3B9EC2C24B692004A32A4 /* PrivacyInfo.xcprivacy in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + C1B3B9C52C24B39E004A32A4 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + C1B3BA2A2C24B971004A32A4 /* Auth0.plist in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ @@ -1609,6 +1934,44 @@ shellPath = /bin/sh; shellScript = "AUTH0_PLIST=\"${SRCROOT}/Auth0.plist\"\n\nif [ -f $AUTH0_PLIST ]; then\n cp \"$AUTH0_PLIST\" \"${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app\"\nfi\n"; }; + C1B3B9ED2C24B699004A32A4 /* SwiftLint */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = SwiftLint; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "if [ -z ${CI} ]; then\n if [[ \"$(uname -m)\" == arm64 ]]; then\n export PATH=\"/opt/homebrew/bin:$PATH\"\n fi\n\n if which swiftlint > /dev/null; then\n swiftlint\n else\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\n fi\nfi\n"; + }; + C1B3BA272C24B864004A32A4 /* Auth0 */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = Auth0; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "AUTH0_PLIST=\"${SRCROOT}/Auth0.plist\"\n\nif [ -f $AUTH0_PLIST ]; then\n cp \"$AUTH0_PLIST\" \"${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app\"\nfi\n"; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -1779,9 +2142,11 @@ 5CB41D7623D0C15000074024 /* IDTokenValidatorBaseSpec.swift in Sources */, 5B9262C31ECF0CC200F4F6D3 /* BioAuthenticationSpec.swift in Sources */, 5C809D96275F878E00F15A67 /* CredentialsManagerErrorSpec.swift in Sources */, + C177D7752C2BE00D0094C657 /* StubURLProtocol.swift in Sources */, 5FBBF0381CC964BC0024D2AF /* Matchers.swift in Sources */, 5F93BC0B1CC6B0DE0031519F /* Auth0Spec.swift in Sources */, 5FE2F8A61CCA9C17003628F4 /* CredentialsSpec.swift in Sources */, + C177D7702C2BDFE40094C657 /* NetworkStub.swift in Sources */, 5CF539242836DCC10073F623 /* SafariProviderSpec.swift in Sources */, 5FADB6091CED500900D4BB50 /* ManagementSpec.swift in Sources */, 5FCAB16D1D07AC3500331C84 /* WebAuthSpec.swift in Sources */, @@ -1824,9 +2189,11 @@ 5FE2F8A71CCA9C17003628F4 /* CredentialsSpec.swift in Sources */, 5CE775A2244FCF2000D054A0 /* Generators.swift in Sources */, 5C809D97275F878E00F15A67 /* CredentialsManagerErrorSpec.swift in Sources */, + C177D7762C2BE00D0094C657 /* StubURLProtocol.swift in Sources */, 5CE775AF244FD66D00D054A0 /* OAuth2GrantSpec.swift in Sources */, 5FD255B21D14A9E000387ECB /* AuthenticationErrorSpec.swift in Sources */, 5C53A7E92703A23200A7C0A3 /* UserInfoSpec.swift in Sources */, + C177D7712C2BDFE40094C657 /* NetworkStub.swift in Sources */, 5CF539252836DCC10073F623 /* SafariProviderSpec.swift in Sources */, 5FADB60A1CED500900D4BB50 /* ManagementSpec.swift in Sources */, 5F28B4681D8300D50000EB23 /* LoggerSpec.swift in Sources */, @@ -1938,12 +2305,14 @@ 5F28B4691D8300D50000EB23 /* LoggerSpec.swift in Sources */, 5F1FBB9A1D8A44C0006B0B85 /* ResponseSpec.swift in Sources */, 5F331B0C1D4BB7F900AE4382 /* UsersSpec.swift in Sources */, + C12BFE442C352DD700D1CC00 /* StubURLProtocol.swift in Sources */, 5F331B0E1D4BB80700AE4382 /* Matchers.swift in Sources */, 5F331B0A1D4BB7F900AE4382 /* ManagementSpec.swift in Sources */, 5F331B091D4BB7EE00AE4382 /* AuthenticationErrorSpec.swift in Sources */, 5F331B101D4BB80700AE4382 /* Auth0Spec.swift in Sources */, 5C809D9C275FA3F000F15A67 /* ManagementErrorSpec.swift in Sources */, 5C809D98275F878E00F15A67 /* CredentialsManagerErrorSpec.swift in Sources */, + C12BFE432C352DD400D1CC00 /* NetworkStub.swift in Sources */, 5F331B041D4BB78C00AE4382 /* TelemetrySpec.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1957,6 +2326,123 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + C1B3B9A42C24B297004A32A4 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + C1B3B9B12C24B297004A32A4 /* ContentView.swift in Sources */, + C1B3B9AF2C24B297004A32A4 /* OAuth2VisionApp.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + C1B3B9BC2C24B39E004A32A4 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + C1B3BA262C24B6F5004A32A4 /* Documentation.docc in Sources */, + C1B3B9EE2C24B6D4004A32A4 /* Auth0Authentication.swift in Sources */, + C1B3B9EF2C24B6D4004A32A4 /* Authentication.swift in Sources */, + C1B3B9F02C24B6D4004A32A4 /* AuthenticationError.swift in Sources */, + C1B3B9F12C24B6D4004A32A4 /* PasswordlessType.swift in Sources */, + C1B3B9F22C24B6D4004A32A4 /* Challenge.swift in Sources */, + C1B3B9F32C24B6D4004A32A4 /* JWKS.swift in Sources */, + C1B3B9F42C24B6D4004A32A4 /* UserInfo.swift in Sources */, + C1B3B9F52C24B6D4004A32A4 /* Credentials.swift in Sources */, + C1B3B9F62C24B6D4004A32A4 /* Handlers.swift in Sources */, + C1B3B9F72C24B6D4004A32A4 /* Telemetry.swift in Sources */, + C1B3B9F82C24B6D4004A32A4 /* IDTokenValidator.swift in Sources */, + C1B3B9F92C24B6D4004A32A4 /* IDTokenValidatorContext.swift in Sources */, + C1B3B9FA2C24B6D4004A32A4 /* IDTokenSignatureValidator.swift in Sources */, + C1B3B9FB2C24B6D4004A32A4 /* ClaimValidators.swift in Sources */, + C1B3B9FC2C24B6D4004A32A4 /* Helpers.swift in Sources */, + C1B3B9FD2C24B6D4004A32A4 /* BioAuthentication.swift in Sources */, + C1B3B9FE2C24B6D4004A32A4 /* CredentialsManager.swift in Sources */, + C1B3B9FF2C24B6D4004A32A4 /* CredentialsStorage.swift in Sources */, + C1B3BA002C24B6D4004A32A4 /* CredentialsManagerError.swift in Sources */, + C1B3BA012C24B6D4004A32A4 /* Array+Encode.swift in Sources */, + C1B3BA022C24B6D4004A32A4 /* String+URLSafe.swift in Sources */, + C1B3BA032C24B6D4004A32A4 /* NSData+URLSafe.swift in Sources */, + C1B3BA042C24B6D4004A32A4 /* NSURL+Auth0.swift in Sources */, + C1B3BA052C24B6D4004A32A4 /* NSURLComponents+OAuth2.swift in Sources */, + C1B3BA062C24B6D4004A32A4 /* JWT+Header.swift in Sources */, + C1B3BA072C24B6D4004A32A4 /* JWK+RSA.swift in Sources */, + C1B3BA082C24B6D4004A32A4 /* Optional+DebugDescription.swift in Sources */, + C1B3BA092C24B6D4004A32A4 /* Logger.swift in Sources */, + C1B3BA0A2C24B6D4004A32A4 /* Loggable.swift in Sources */, + C1B3BA0B2C24B6D4004A32A4 /* Management.swift in Sources */, + C1B3BA0C2C24B6D4004A32A4 /* UserPatchAttributes.swift in Sources */, + C1B3BA0D2C24B6D4004A32A4 /* Users.swift in Sources */, + C1B3BA0E2C24B6D4004A32A4 /* ManagementError.swift in Sources */, + C1B3BA0F2C24B6D4004A32A4 /* JSONObjectPayload.swift in Sources */, + C1B3BA102C24B6D4004A32A4 /* Request.swift in Sources */, + C1B3BA112C24B6D4004A32A4 /* Requestable.swift in Sources */, + C1B3BA122C24B6D4004A32A4 /* Response.swift in Sources */, + C1B3BA132C24B6D4004A32A4 /* JWTAlgorithm.swift in Sources */, + C1B3BA142C24B6D4004A32A4 /* ChallengeGenerator.swift in Sources */, + C1B3BA152C24B6D4004A32A4 /* ASProvider.swift in Sources */, + C1B3BA172C24B6D4004A32A4 /* LoginTransaction.swift in Sources */, + C1B3BA182C24B6D4004A32A4 /* ClearSessionTransaction.swift in Sources */, + C1B3BA192C24B6D4004A32A4 /* MobileWebAuth.swift in Sources */, + C1B3BA1B2C24B6D4004A32A4 /* AuthTransaction.swift in Sources */, + C1B3BA1C2C24B6D4004A32A4 /* WebAuthUserAgent.swift in Sources */, + C1B3BA1D2C24B6D4004A32A4 /* OAuth2Grant.swift in Sources */, + C1B3BA1E2C24B6D4004A32A4 /* TransactionStore.swift in Sources */, + C1B3BA1F2C24B6D4004A32A4 /* WebAuth.swift in Sources */, + C1B3BA202C24B6D4004A32A4 /* Auth0WebAuth.swift in Sources */, + C1B3BA212C24B6D4004A32A4 /* WebAuthError.swift in Sources */, + C1B3BA222C24B6D4004A32A4 /* WebAuthentication.swift in Sources */, + C1B3BA232C24B6D4004A32A4 /* Auth0.swift in Sources */, + C1B3BA242C24B6D4004A32A4 /* Auth0Error.swift in Sources */, + C1B3BA252C24B6D4004A32A4 /* Version.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + C1B3B9C32C24B39E004A32A4 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + C1B3BA2B2C24BA36004A32A4 /* LoggerSpec.swift in Sources */, + C1B3BA2C2C24BA36004A32A4 /* TelemetrySpec.swift in Sources */, + C1B3BA2D2C24BA36004A32A4 /* AuthenticationSpec.swift in Sources */, + C1B3BA2E2C24BA36004A32A4 /* CredentialsSpec.swift in Sources */, + C1B3BA2F2C24BA36004A32A4 /* UserInfoSpec.swift in Sources */, + C1B3BA302C24BA36004A32A4 /* JWKSpec.swift in Sources */, + C1B3BA312C24BA36004A32A4 /* AuthenticationErrorSpec.swift in Sources */, + C1B3BA322C24BA36004A32A4 /* ManagementSpec.swift in Sources */, + C1B3BA332C24BA36004A32A4 /* ManagementErrorSpec.swift in Sources */, + C1B3BA342C24BA36004A32A4 /* UserPatchAttributesSpec.swift in Sources */, + C1B3BA352C24BA36004A32A4 /* UsersSpec.swift in Sources */, + C1B3BA362C24BA36004A32A4 /* ResponseSpec.swift in Sources */, + C1B3BA372C24BA36004A32A4 /* RequestSpec.swift in Sources */, + C1B3BA382C24BA36004A32A4 /* JWTAlgorithmSpec.swift in Sources */, + C1B3BA392C24BA36004A32A4 /* LoginTransactionSpec.swift in Sources */, + C1B3BA3A2C24BA36004A32A4 /* ClearSessionTransactionSpec.swift in Sources */, + C1B3BA3B2C24BA36004A32A4 /* ASProviderSpec.swift in Sources */, + C1B3BA3D2C24BA36004A32A4 /* WebAuthErrorSpec.swift in Sources */, + C177D7772C2BE00D0094C657 /* StubURLProtocol.swift in Sources */, + C1B3BA3E2C24BA36004A32A4 /* ChallengeGeneratorSpec.swift in Sources */, + C1B3BA3F2C24BA36004A32A4 /* OAuth2GrantSpec.swift in Sources */, + C1B3BA402C24BA36004A32A4 /* WebAuthSpec.swift in Sources */, + C177D7722C2BDFE40094C657 /* NetworkStub.swift in Sources */, + C1B3BA412C24BA36004A32A4 /* TransactionStoreSpec.swift in Sources */, + C1B3BA422C24BA36004A32A4 /* WebAuthenticationSpec.swift in Sources */, + C1B3BA432C24BA36004A32A4 /* WebAuthSpies.swift in Sources */, + C1B3BA442C24BA36004A32A4 /* IDTokenValidatorBaseSpec.swift in Sources */, + C1B3BA452C24BA36004A32A4 /* IDTokenValidatorMocks.swift in Sources */, + C1B3BA462C24BA36004A32A4 /* IDTokenValidatorSpec.swift in Sources */, + C1B3BA472C24BA36004A32A4 /* IDTokenSignatureValidatorSpec.swift in Sources */, + C1B3BA482C24BA36004A32A4 /* ClaimValidatorsSpec.swift in Sources */, + C1B3BA492C24BA37004A32A4 /* BioAuthenticationSpec.swift in Sources */, + C1B3BA4A2C24BA37004A32A4 /* CredentialsManagerSpec.swift in Sources */, + C1B3BA4B2C24BA37004A32A4 /* CredentialsManagerErrorSpec.swift in Sources */, + C1B3BA4C2C24BA37004A32A4 /* CryptoExtensions.swift in Sources */, + C1B3BA4D2C24BA37004A32A4 /* Matchers.swift in Sources */, + C1B3BA4E2C24BA37004A32A4 /* Responses.swift in Sources */, + C1B3BA4F2C24BA37004A32A4 /* Generators.swift in Sources */, + C1B3BA502C24BA37004A32A4 /* Auth0Spec.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ @@ -1990,6 +2476,16 @@ target = 5F3965C61CF67DD800CDE7C0 /* OAuth2 */; targetProxy = 5FCAB1641D07ABC000331C84 /* PBXContainerItemProxy */; }; + C1B3B9CC2C24B39E004A32A4 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = C1B3B9A72C24B297004A32A4 /* OAuth2Vision */; + targetProxy = C1B3B9CB2C24B39E004A32A4 /* PBXContainerItemProxy */; + }; + C1B3BA292C24B95A004A32A4 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = C1B3B9BF2C24B39E004A32A4 /* Auth0.visionOS */; + targetProxy = C1B3BA282C24B95A004A32A4 /* PBXContainerItemProxy */; + }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ @@ -2059,7 +2555,7 @@ SWIFT_SWIFT3_OBJC_INFERENCE = Default; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 3; - TVOS_DEPLOYMENT_TARGET = 13.0; + TVOS_DEPLOYMENT_TARGET = 14.0; }; name = Debug; }; @@ -2091,7 +2587,7 @@ SWIFT_SWIFT3_OBJC_INFERENCE = Default; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 3; - TVOS_DEPLOYMENT_TARGET = 13.0; + TVOS_DEPLOYMENT_TARGET = 14.0; }; name = Release; }; @@ -2207,7 +2703,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; MACOSX_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; @@ -2263,7 +2759,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; MACOSX_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; @@ -2291,7 +2787,7 @@ FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)/Carthage/Build"; INFOPLIST_FILE = Auth0/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -2322,7 +2818,7 @@ FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)/Carthage/Build"; INFOPLIST_FILE = Auth0/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -2416,7 +2912,7 @@ CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = Auth0Tests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -2442,7 +2938,7 @@ CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = Auth0Tests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -2598,7 +3094,7 @@ SWIFT_SWIFT3_OBJC_INFERENCE = Default; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 3; - TVOS_DEPLOYMENT_TARGET = 13.0; + TVOS_DEPLOYMENT_TARGET = 14.0; }; name = Debug; }; @@ -2627,7 +3123,7 @@ SWIFT_SWIFT3_OBJC_INFERENCE = Default; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 3; - TVOS_DEPLOYMENT_TARGET = 13.0; + TVOS_DEPLOYMENT_TARGET = 14.0; }; name = Release; }; @@ -2651,7 +3147,7 @@ SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2,3"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/OAuth2TV.app/OAuth2TV"; - TVOS_DEPLOYMENT_TARGET = 13.0; + TVOS_DEPLOYMENT_TARGET = 14.0; }; name = Debug; }; @@ -2675,7 +3171,7 @@ SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2,3"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/OAuth2TV.app/OAuth2TV"; - TVOS_DEPLOYMENT_TARGET = 13.0; + TVOS_DEPLOYMENT_TARGET = 14.0; }; name = Release; }; @@ -2690,7 +3186,7 @@ CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = App/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -2714,7 +3210,7 @@ CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = App/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -2726,6 +3222,260 @@ }; name = Release; }; + C1B3B9B82C24B298004A32A4 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "$(EMBEDDED_CONTENT_CONTAINS_SWIFT)"; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_OBJC_WEAK = NO; + CLANG_WARN_DOCUMENTATION_COMMENTS = NO; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = ""; + ENABLE_PREVIEWS = NO; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = "$(TARGET_NAME)/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + LOCALIZATION_PREFERS_STRING_CATALOGS = NO; + MARKETING_VERSION = 1.0; + MTL_ENABLE_DEBUG_INFO = YES; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.auth0.OAuth2Vision; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = xros; + SUPPORTED_PLATFORMS = "xros xrsimulator"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = ""; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2,7"; + XROS_DEPLOYMENT_TARGET = 1.0; + }; + name = Debug; + }; + C1B3B9B92C24B298004A32A4 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "$(EMBEDDED_CONTENT_CONTAINS_SWIFT)"; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_OBJC_WEAK = NO; + CLANG_WARN_DOCUMENTATION_COMMENTS = NO; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = ""; + ENABLE_PREVIEWS = NO; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = NO; + INFOPLIST_FILE = App/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + LOCALIZATION_PREFERS_STRING_CATALOGS = NO; + MARKETING_VERSION = 1.0; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.auth0.OAuth2Vision; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = xros; + SUPPORTED_PLATFORMS = "xros xrsimulator"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2,7"; + XROS_DEPLOYMENT_TARGET = 1.0; + }; + name = Release; + }; + C1B3B9D62C24B39E004A32A4 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + BUILD_LIBRARY_FOR_DISTRIBUTION = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_OBJC_WEAK = NO; + CLANG_WARN_DOCUMENTATION_COMMENTS = NO; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_MODULE_VERIFIER = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)/Carthage/Build"; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_PREPROCESSOR_DEFINITIONS = ""; + GENERATE_INFOPLIST_FILE = NO; + INFOPLIST_FILE = Auth0/Info.plist; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + LOCALIZATION_PREFERS_STRING_CATALOGS = NO; + MARKETING_VERSION = ""; + MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20"; + MTL_ENABLE_DEBUG_INFO = YES; + MTL_FAST_MATH = YES; + OTHER_SWIFT_FLAGS = "-DDEBUG"; + PRODUCT_BUNDLE_IDENTIFIER = com.auth0.Auth0; + PRODUCT_NAME = Auth0; + SDKROOT = xros; + SKIP_INSTALL = YES; + SUPPORTED_PLATFORMS = "xros xrsimulator"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = WEB_AUTH_PLATFORM; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_INSTALL_OBJC_HEADER = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2,7"; + XROS_DEPLOYMENT_TARGET = 1.0; + }; + name = Debug; + }; + C1B3B9D72C24B39E004A32A4 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + BUILD_LIBRARY_FOR_DISTRIBUTION = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_OBJC_WEAK = NO; + CLANG_WARN_DOCUMENTATION_COMMENTS = NO; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_MODULE_VERIFIER = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)/Carthage/Build"; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = NO; + INFOPLIST_FILE = Auth0/Info.plist; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + LOCALIZATION_PREFERS_STRING_CATALOGS = NO; + MARKETING_VERSION = ""; + MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20"; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.auth0.Auth0; + PRODUCT_NAME = Auth0; + SDKROOT = xros; + SKIP_INSTALL = YES; + SUPPORTED_PLATFORMS = "xros xrsimulator"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = WEB_AUTH_PLATFORM; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_INSTALL_OBJC_HEADER = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2,7"; + XROS_DEPLOYMENT_TARGET = 1.0; + }; + name = Release; + }; + C1B3B9DA2C24B39E004A32A4 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GENERATE_INFOPLIST_FILE = YES; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 1.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.auth0.Auth0Tests; + PRODUCT_NAME = Auth0Tests; + SDKROOT = xros; + SUPPORTED_PLATFORMS = "xros xrsimulator"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = WEB_AUTH_PLATFORM; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2,7"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/OAuth2Vision.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/OAuth2Vision"; + XROS_DEPLOYMENT_TARGET = 1.2; + }; + name = Debug; + }; + C1B3B9DB2C24B39E004A32A4 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 1.0; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.auth0.Auth0Tests; + PRODUCT_NAME = Auth0Tests; + SDKROOT = xros; + SUPPORTED_PLATFORMS = "xros xrsimulator"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = WEB_AUTH_PLATFORM; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2,7"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/OAuth2Vision.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/OAuth2Vision"; + XROS_DEPLOYMENT_TARGET = 1.2; + }; + name = Release; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -2828,7 +3578,68 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + C1B3B9BA2C24B298004A32A4 /* Build configuration list for PBXNativeTarget "OAuth2Vision" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + C1B3B9B82C24B298004A32A4 /* Debug */, + C1B3B9B92C24B298004A32A4 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + C1B3B9D52C24B39E004A32A4 /* Build configuration list for PBXNativeTarget "Auth0.visionOS" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + C1B3B9D62C24B39E004A32A4 /* Debug */, + C1B3B9D72C24B39E004A32A4 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + C1B3B9D92C24B39E004A32A4 /* Build configuration list for PBXNativeTarget "Auth0Tests.visionOS" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + C1B3B9DA2C24B39E004A32A4 /* Debug */, + C1B3B9DB2C24B39E004A32A4 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; /* End XCConfigurationList section */ + +/* Begin XCRemoteSwiftPackageReference section */ + C177D6C52C2ADEB60094C657 /* XCRemoteSwiftPackageReference "CwlPreconditionTesting" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/mattgallagher/CwlPreconditionTesting.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 2.2.1; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + C177D6C62C2ADEB60094C657 /* CwlPreconditionTesting */ = { + isa = XCSwiftPackageProductDependency; + package = C177D6C52C2ADEB60094C657 /* XCRemoteSwiftPackageReference "CwlPreconditionTesting" */; + productName = CwlPreconditionTesting; + }; + C177D6D32C2B0DCB0094C657 /* CwlPreconditionTesting */ = { + isa = XCSwiftPackageProductDependency; + package = C177D6C52C2ADEB60094C657 /* XCRemoteSwiftPackageReference "CwlPreconditionTesting" */; + productName = CwlPreconditionTesting; + }; + C19413DB2C2D792200FE88F7 /* CwlPreconditionTesting */ = { + isa = XCSwiftPackageProductDependency; + package = C177D6C52C2ADEB60094C657 /* XCRemoteSwiftPackageReference "CwlPreconditionTesting" */; + productName = CwlPreconditionTesting; + }; + C196EB062C35D0C700D108AA /* CwlPreconditionTesting */ = { + isa = XCSwiftPackageProductDependency; + package = C177D6C52C2ADEB60094C657 /* XCRemoteSwiftPackageReference "CwlPreconditionTesting" */; + productName = CwlPreconditionTesting; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = 5F049B601CB42C29006F6C05 /* Project object */; } diff --git a/Auth0.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Auth0.xcodeproj/project.xcworkspace/contents.xcworkspacedata index 09240b71..919434a6 100644 --- a/Auth0.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ b/Auth0.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -2,6 +2,6 @@ + location = "self:"> diff --git a/Auth0.xcodeproj/xcshareddata/xcschemes/Auth0.visionOS.xcscheme b/Auth0.xcodeproj/xcshareddata/xcschemes/Auth0.visionOS.xcscheme new file mode 100644 index 00000000..0d23ea59 --- /dev/null +++ b/Auth0.xcodeproj/xcshareddata/xcschemes/Auth0.visionOS.xcscheme @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Auth0/ASProvider.swift b/Auth0/ASProvider.swift index aa845191..8f059612 100644 --- a/Auth0/ASProvider.swift +++ b/Auth0/ASProvider.swift @@ -10,7 +10,7 @@ extension WebAuthentication { let session: ASWebAuthenticationSession #if compiler(>=5.10) - if #available(iOS 17.4, macOS 14.4, *) { + if #available(iOS 17.4, macOS 14.4, visionOS 1.2, *) { if redirectURL.scheme == "https" { session = ASWebAuthenticationSession(url: url, callback: .https(host: redirectURL.host!, diff --git a/Auth0/Auth0WebAuth.swift b/Auth0/Auth0WebAuth.swift index 3564fa9f..8d13cf1b 100644 --- a/Auth0/Auth0WebAuth.swift +++ b/Auth0/Auth0WebAuth.swift @@ -14,8 +14,10 @@ final class Auth0WebAuth: WebAuth { #if os(macOS) private let platform = "macos" - #else + #elseif os(iOS) private let platform = "ios" + #else + private let platform = "visionos" #endif private let responseType = "code" diff --git a/Auth0/MobileWebAuth.swift b/Auth0/MobileWebAuth.swift index 8e0f5367..274a1aeb 100644 --- a/Auth0/MobileWebAuth.swift +++ b/Auth0/MobileWebAuth.swift @@ -1,20 +1,31 @@ -#if os(iOS) +#if os(iOS) || os(visionOS) import UIKit import AuthenticationServices extension UIApplication { - + static func shared() -> UIApplication? { return UIApplication.perform(NSSelectorFromString("sharedApplication"))?.takeUnretainedValue() as? UIApplication } - + } extension ASUserAgent: ASWebAuthenticationPresentationContextProviding { - + +#if os(iOS) func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor { return UIApplication.shared()?.windows.last(where: \.isKeyWindow) ?? ASPresentationAnchor() } - +#endif + +#if os(visionOS) + func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor { + if let windowScene = UIApplication.shared()?.connectedScenes.first(where: { $0.activationState == .foregroundActive }) as? UIWindowScene { + return windowScene.windows.last(where: \.isKeyWindow) ?? ASPresentationAnchor() + } + return ASPresentationAnchor() + } +#endif + } #endif diff --git a/Auth0/Telemetry.swift b/Auth0/Telemetry.swift index 2befabb9..f860c8e2 100644 --- a/Auth0/Telemetry.swift +++ b/Auth0/Telemetry.swift @@ -85,6 +85,8 @@ public struct Telemetry { return "tvOS" #elseif os(watchOS) return "watchOS" + #elseif os(visionOS) + return "visionOS" #else return "unknown" #endif diff --git a/Auth0Tests/ASProviderSpec.swift b/Auth0Tests/ASProviderSpec.swift index acc3cda7..b9cf2821 100644 --- a/Auth0Tests/ASProviderSpec.swift +++ b/Auth0Tests/ASProviderSpec.swift @@ -11,54 +11,54 @@ private let CustomSchemeRedirectURL = URL(string: "com.auth0.example://samples.a private let Timeout: NimbleTimeInterval = .seconds(2) class ASProviderSpec: QuickSpec { - - override func spec() { - + + override class func spec() { + var session: ASWebAuthenticationSession! var userAgent: ASUserAgent! - + beforeEach { session = ASWebAuthenticationSession(url: AuthorizeURL, callbackURLScheme: nil, completionHandler: { _, _ in }) userAgent = ASUserAgent(session: session, callback: { _ in }) } - + afterEach { session.cancel() } - + describe("WebAuthentication extension") { - + it("should create a web authentication session provider") { let provider = WebAuthentication.asProvider(redirectURL: HTTPSRedirectURL) expect(provider(AuthorizeURL, {_ in })).to(beAKindOf(ASUserAgent.self)) } - + it("should not use an ephemeral session by default") { let provider = WebAuthentication.asProvider(redirectURL: CustomSchemeRedirectURL) userAgent = provider(AuthorizeURL, { _ in }) as? ASUserAgent expect(userAgent.session.prefersEphemeralWebBrowserSession) == false } - + it("should use an ephemeral session") { let provider = WebAuthentication.asProvider(redirectURL: CustomSchemeRedirectURL, ephemeralSession: true) userAgent = provider(AuthorizeURL, { _ in }) as? ASUserAgent expect(userAgent.session.prefersEphemeralWebBrowserSession) == true } - + } - + describe("user agent") { - + it("should have a custom description") { expect(userAgent.description) == "ASWebAuthenticationSession" } - + it("should be the web authentication session's presentation context provider") { expect(session.presentationContextProvider).to(be(userAgent)) } - + it("should call the callback with an error") { - await waitUntil(timeout: Timeout) { done in + waitUntil(timeout: Timeout) { done in let userAgent = ASUserAgent(session: session, callback: { result in expect(result).to(beFailure()) done() @@ -66,9 +66,9 @@ class ASProviderSpec: QuickSpec { userAgent.finish(with: .failure(.userCancelled)) } } - + it("should call the callback with success") { - await waitUntil(timeout: Timeout) { done in + waitUntil(timeout: Timeout) { done in let userAgent = ASUserAgent(session: session, callback: { result in expect(result).to(beSuccessful()) done() @@ -76,9 +76,8 @@ class ASProviderSpec: QuickSpec { userAgent.finish(with: .success(())) } } - } - + } - + } diff --git a/Auth0Tests/Auth0Spec.swift b/Auth0Tests/Auth0Spec.swift index 4e4316b8..ae542ed5 100644 --- a/Auth0Tests/Auth0Spec.swift +++ b/Auth0Tests/Auth0Spec.swift @@ -10,7 +10,7 @@ private let Token = "TOKEN" class Auth0Spec: QuickSpec { - override func spec() { + override class func spec() { describe("global functions") { diff --git a/Auth0Tests/AuthenticationErrorSpec.swift b/Auth0Tests/AuthenticationErrorSpec.swift index e5e92845..2b42a862 100644 --- a/Auth0Tests/AuthenticationErrorSpec.swift +++ b/Auth0Tests/AuthenticationErrorSpec.swift @@ -11,13 +11,9 @@ private let ExampleValuesKey = "com.auth0.authentication.example.values.key" private let ExamplePlainValueKey = "com.auth0.authentication.example.value.key" private let ExamplePlainStatusKey = "com.auth0.authentication.example.status.key" -private let UnknownErrorExample = "com.auth0.authentication.example.unknown" -private let PlainErrorExample = "com.auth0.authentication.example.plain" -private let Auth0ErrorExample = "com.auth0.authentication.example.auth0" -private let OAuthErrorExample = "com.auth0.authentication.example.oauth" class AuthenticationErrorSpec: QuickSpec { - override func spec() { + override class func spec() { describe("init") { @@ -146,14 +142,14 @@ class AuthenticationErrorSpec: QuickSpec { describe("oauth spec error") { - itBehavesLike(OAuthErrorExample) { + itBehavesLike(OAuthErrorBehavior.self) { return [ ExampleCodeKey: "invalid_request", ExampleDescriptionKey: "missing client_id", ] } - itBehavesLike(OAuthErrorExample) { + itBehavesLike(OAuthErrorBehavior.self) { return [ ExampleCodeKey: "invalid_request", ] @@ -168,21 +164,21 @@ class AuthenticationErrorSpec: QuickSpec { describe("unknown error structure") { - itBehavesLike(UnknownErrorExample) { return [ExampleValuesKey: ["key": "value"]] } - itBehavesLike(UnknownErrorExample) { return [ExampleValuesKey: [String: String]()] } + itBehavesLike(UnknownErrorBehavior.self) { return [ExampleValuesKey: ["key": "value"]] } + itBehavesLike(UnknownErrorBehavior.self) { return [ExampleValuesKey: [String: String]()] } } describe("non json") { - itBehavesLike(PlainErrorExample) { return [ExamplePlainValueKey: "random string"] } - itBehavesLike(PlainErrorExample) { return [ExamplePlainStatusKey: 200] } + itBehavesLike(PlainErrorBehavior.self) { return [ExamplePlainValueKey: "random string"] } + itBehavesLike(PlainErrorBehavior.self) { return [ExamplePlainStatusKey: 200] } } describe("auth0 error") { - itBehavesLike(Auth0ErrorExample) { return [ + itBehavesLike(Auth0ErrorBehavior.self) { return [ ExampleCodeKey: "invalid_password", ExampleDescriptionKey: "You may not reuse any of the last 2 passwords. This password was used a day ago.", ExampleExtraKey: [ @@ -442,133 +438,160 @@ class AuthenticationErrorSpec: QuickSpec { } -class AuthenticationErrorSpecSharedExamplesConfiguration: QuickConfiguration { - override class func configure(_ configuration: QCKConfiguration) { - sharedExamples(OAuthErrorExample) { (context: SharedExampleContext) in - let code = context()[ExampleCodeKey] as! String - let description = context()[ExampleDescriptionKey] as? String - var values = [ - "error": code, - ] - values["error_description"] = description - let error = AuthenticationError(info: values, statusCode: 401) - - it("should have code \(code)") { - expect(error.code) == code - } - - if let description = description { - it("should have description \(description)") { - expect(error.localizedDescription) == description - } +class OAuthErrorBehavior: Behavior<[String:Any]> { + override class func spec(_ aContext: @escaping () -> [String : Any]) { + var context: [String:Any]! + var error: AuthenticationError! + + beforeEach { + context = aContext() + error = AuthenticationError(info: getValuesFromBehaviourContext(context), statusCode: 401) + } + + it("should have code") { + expect(error.code) == context[ExampleCodeKey] as! String + } + + it("should have description") { + if let description = context[ExampleDescriptionKey] as? String { + expect(error.localizedDescription) == description } else { - it("should have a description") { - expect(error.localizedDescription).toNot(beNil()) - } + expect(error.localizedDescription).toNot(beNil()) } - + } + + it("should contain info key value pairs") { + let values = getValuesFromBehaviourContext(context) values.forEach { key, value in - it("should contain \(key)") { - expect(error.info[key] as? String) == value - } + expect(error.info[key] as? String) == value } - - it("should not match any custom error") { - expect(error.isRuleError).to(beFalse(), description: "should not match rule error") - expect(error.isPasswordNotStrongEnough).to(beFalse(), description: "should not match password strength") - expect(error.isPasswordAlreadyUsed).to(beFalse(), description: "should not match password history") - expect(error.isInvalidCredentials).to(beFalse(), description: "should not match invalid credentials") - expect(error.isRefreshTokenDeleted).to(beFalse(), description: "should not match refresh token deleted") - expect(error.isInvalidRefreshToken).to(beFalse(), description: "should not match invalid refresh token") - expect(error.isPasswordLeaked).to(beFalse(), description: "should not match password leaked") - expect(error.isLoginRequired).to(beFalse(), description: "should not match login required") - } - } + + it("should not match any custom error") { + expect(error.isRuleError).to(beFalse(), description: "should not match rule error") + expect(error.isPasswordNotStrongEnough).to(beFalse(), description: "should not match password strength") + expect(error.isPasswordAlreadyUsed).to(beFalse(), description: "should not match password history") + expect(error.isInvalidCredentials).to(beFalse(), description: "should not match invalid credentials") + expect(error.isRefreshTokenDeleted).to(beFalse(), description: "should not match refresh token deleted") + expect(error.isInvalidRefreshToken).to(beFalse(), description: "should not match invalid refresh token") + expect(error.isPasswordLeaked).to(beFalse(), description: "should not match password leaked") + expect(error.isLoginRequired).to(beFalse(), description: "should not match login required") + } + } +} - sharedExamples(Auth0ErrorExample) { (context: SharedExampleContext) in - let code = context()[ExampleCodeKey] as! String - let description = context()[ExampleDescriptionKey] as! String - let extras = context()[ExampleExtraKey] as? [String: String] - var values = [ - "code": code, - "description": description, - ] - extras?.forEach { values[$0] = $1 } - let error = AuthenticationError(info: values, statusCode: 401) - - it("should have code \(code)") { - expect(error.code) == code - } - - it("should have localized description \(description)") { - expect(error.localizedDescription) == description - } - +class Auth0ErrorBehavior: Behavior<[String:Any]> { + override class func spec(_ aContext: @escaping () -> [String : Any]) { + var context: [String:Any]! + var error: AuthenticationError! + + beforeEach { + context = aContext() + error = AuthenticationError(info: getValuesFromBehaviourContext(context), statusCode: 401) + } + + it("should have code") { + expect(error.code) == context[ExampleCodeKey] as! String + } + + it("should have localized description") { + expect(error.localizedDescription) == context[ExampleDescriptionKey] as! String + } + + it("should contain info key value pairs") { + let values = getValuesFromBehaviourContext(context) values.forEach { key, value in - it("should contain \(key)") { - expect(error.info[key] as? String) == value - } + expect(error.info[key] as? String) == value } - + } + + it("should contain the extras key value pairs") { + let extras = context[ExampleExtraKey] as? [String: String] extras?.forEach { key, value in - it("should have key \(key) with value \(value)") { - expect(error.info[key] as? String) == value - } + expect(error.info[key] as? String) == value } } + } +} - sharedExamples(UnknownErrorExample) { (context: SharedExampleContext) in - let values = context()[ExampleValuesKey] as! [String: Any] - let error = AuthenticationError(info: values, statusCode: 401) - it("should have unknown error code") { - expect(error.code) == unknownError - } - - it("should have localized description") { - expect(error.localizedDescription).toNot(beNil()) - } - +class UnknownErrorBehavior: Behavior<[String:Any]> { + override class func spec(_ aContext: @escaping () -> [String : Any]) { + var context: [String:Any]! + var error: AuthenticationError! + + beforeEach { + context = aContext() + error = AuthenticationError(info: getValuesFromBehaviourContext(context), statusCode: 401) + } + + it("should have unknown error code") { + expect(error.code) == unknownError + } + + it("should have localized description") { + expect(error.localizedDescription).toNot(beNil()) + } + + it("should contain info key value pairs") { + let values = getValuesFromBehaviourContext(context) values.forEach { key, value in - it("should contain \(key)") { - expect(error.info[key]).toNot(beNil()) - } - } - - it("should not match any known error") { - expect(error.isMultifactorCodeInvalid).to(beFalse(), description: "should not match mfa otp invalid") - expect(error.isMultifactorEnrollRequired).to(beFalse(), description: "should not match mfa enroll") - expect(error.isMultifactorTokenInvalid).to(beFalse(), description: "should not match mfa token invalid") - expect(error.isMultifactorRequired).to(beFalse(), description: "should not match mfa missing") - expect(error.isRuleError).to(beFalse(), description: "should not match rule error") - expect(error.isPasswordNotStrongEnough).to(beFalse(), description: "should not match password strength") - expect(error.isPasswordAlreadyUsed).to(beFalse(), description: "should not match password history") - expect(error.isAccessDenied).to(beFalse(), description: "should not match access denied") - expect(error.isInvalidCredentials).to(beFalse(), description: "should not match invalid credentials") - expect(error.isRefreshTokenDeleted).to(beFalse(), description: "should not match refresh token deleted") - expect(error.isInvalidRefreshToken).to(beFalse(), description: "should not match invalid refresh token") - expect(error.isPasswordLeaked).to(beFalse(), description: "should not match password leaked") - expect(error.isLoginRequired).to(beFalse(), description: "should not match login required") + expect(error.info[key] as? String) == value } } + + it("should not match any known error") { + expect(error.isMultifactorCodeInvalid).to(beFalse(), description: "should not match mfa otp invalid") + expect(error.isMultifactorEnrollRequired).to(beFalse(), description: "should not match mfa enroll") + expect(error.isMultifactorTokenInvalid).to(beFalse(), description: "should not match mfa token invalid") + expect(error.isMultifactorRequired).to(beFalse(), description: "should not match mfa missing") + expect(error.isRuleError).to(beFalse(), description: "should not match rule error") + expect(error.isPasswordNotStrongEnough).to(beFalse(), description: "should not match password strength") + expect(error.isPasswordAlreadyUsed).to(beFalse(), description: "should not match password history") + expect(error.isAccessDenied).to(beFalse(), description: "should not match access denied") + expect(error.isInvalidCredentials).to(beFalse(), description: "should not match invalid credentials") + expect(error.isRefreshTokenDeleted).to(beFalse(), description: "should not match refresh token deleted") + expect(error.isInvalidRefreshToken).to(beFalse(), description: "should not match invalid refresh token") + expect(error.isPasswordLeaked).to(beFalse(), description: "should not match password leaked") + expect(error.isLoginRequired).to(beFalse(), description: "should not match login required") + } + } +} - sharedExamples(PlainErrorExample) { (context: SharedExampleContext) in - let value = context()[ExamplePlainValueKey] as? String ?? "" - let status = context()[ExamplePlainStatusKey] as? Int ?? 0 - let error = AuthenticationError(description: value, statusCode: status) - - it("should have plain error code") { - expect(error.code) == nonJSONError - } - - it("should have localized description") { - expect(error.localizedDescription) == value - } - - it("should return status code") { - expect(error.info["statusCode"] as? Int).toNot(beNil()) - } +class PlainErrorBehavior: Behavior<[String:Any]> { + override class func spec(_ aContext: @escaping () -> [String : Any]) { + var context: [String:Any]! + var error: AuthenticationError! + + beforeEach { + context = aContext() + let value = context[ExamplePlainValueKey] as? String ?? "" + let status = context[ExamplePlainStatusKey] as? Int ?? 0 + error = AuthenticationError(description:value , statusCode: status) + } + + it("should have plain error code") { + expect(error.code) == nonJSONError + } + + it("should have localized description") { + expect(error.localizedDescription) == context[ExamplePlainValueKey] as? String ?? "" + } + + it("should return status code") { + expect(error.info["statusCode"] as? Int).toNot(beNil()) } + } +} +func getValuesFromBehaviourContext(_ context: [String : Any]) -> [String : String] { + var values: [String:String] = [:] + if let code = context[ExampleCodeKey] as? String { + values["error"] = code + } + if let description = context[ExampleDescriptionKey] as? String { + values["error_description"] = description } + let extras = context[ExampleExtraKey] as? [String: String] + extras?.forEach { values[$0] = $1 } + return values } diff --git a/Auth0Tests/AuthenticationSpec.swift b/Auth0Tests/AuthenticationSpec.swift index 974e58f1..708a1fd1 100644 --- a/Auth0Tests/AuthenticationSpec.swift +++ b/Auth0Tests/AuthenticationSpec.swift @@ -1,10 +1,6 @@ import Foundation import Quick import Nimble -import OHHTTPStubs -#if SWIFT_PACKAGE -import OHHTTPStubsSwift -#endif @testable import Auth0 @@ -25,32 +21,33 @@ private let TokenExchangeGrantType = "urn:ietf:params:oauth:grant-type:token-exc private let PasswordlessGrantType = "http://auth0.com/oauth/grant-type/passwordless/otp" class AuthenticationSpec: QuickSpec { - override func spec() { - + override class func spec() { + let auth: Authentication = Auth0Authentication(clientId: ClientId, url: DomainURL) - + beforeEach { - stub(condition: isHost(Domain)) { _ in catchAllResponse() }.name = "YOU SHALL NOT PASS!" + URLProtocol.registerClass(StubURLProtocol.self) } - + afterEach { - HTTPStubs.removeAllStubs() + NetworkStub.clearStubs() + URLProtocol.unregisterClass(StubURLProtocol.self) } - + describe("init") { - + it("should init with client id & url") { let authentication = Auth0Authentication(clientId: ClientId, url: DomainURL) expect(authentication.clientId) == ClientId expect(authentication.url) == DomainURL } - + it("should init with client id, url & session") { let session = URLSession(configuration: URLSession.shared.configuration) let authentication = Auth0Authentication(clientId: ClientId, url: DomainURL, session: session) expect(authentication.session).to(be(session)) } - + it("should init with client id, url & telemetry") { let telemetryInfo = "info" var telemetry = Telemetry() @@ -58,37 +55,40 @@ class AuthenticationSpec: QuickSpec { let authentication = Auth0Authentication(clientId: ClientId, url: DomainURL, telemetry: telemetry) expect(authentication.telemetry.info) == telemetryInfo } - + } - + describe("login MFA OTP") { - + beforeEach { - stub(condition: isToken(Domain) && hasAtLeast(["otp": OTP, "mfa_token": MFAToken])) { _ in return authResponse(accessToken: AccessToken, idToken: IdToken) }.name = "OpenID Auth" - stub(condition: isToken(Domain) && hasAtLeast(["otp": "bad_otp", "mfa_token": MFAToken])) { _ in return authFailure(code: "invalid_grant", description: "Invalid otp_code.") }.name = "invalid otp" - stub(condition: isToken(Domain) && hasAtLeast(["otp": OTP, "mfa_token": "bad_token"])) { _ in return authFailure(code: "invalid_grant", description: "Malformed mfa_token") }.name = "invalid mfa_token" + NetworkStub.addStub(condition: { $0.isToken(Domain) && $0.hasAtLeast(["otp": OTP, "mfa_token": MFAToken]) + }, response: authResponse(accessToken: AccessToken, idToken: IdToken)) + NetworkStub.addStub(condition: { $0.isToken(Domain) && $0.hasAtLeast(["otp": OTP, "mfa_token": "bad_token"]) + }, response: authFailure(code: "invalid_grant", description: "Malformed mfa_token")) + NetworkStub.addStub(condition: { $0.isToken(Domain) && $0.hasAtLeast(["otp": "bad_otp", "mfa_token": MFAToken]) + }, response: authFailure(code: "invalid_grant", description: "Invalid otp_code.")) } - + it("should login with otp and mfa tokens") { - await waitUntil(timeout: Timeout) { done in + waitUntil(timeout: Timeout) { done in auth.login(withOTP: OTP, mfaToken: MFAToken).start { result in expect(result).to(haveCredentials()) done() } } } - + it("should fail login with bad otp") { - await waitUntil(timeout: Timeout) { done in + waitUntil(timeout: Timeout) { done in auth.login(withOTP: "bad_otp", mfaToken: MFAToken).start { result in expect(result).to(haveAuthenticationError(code: "invalid_grant", description: "Invalid otp_code.")) done() } } } - + it("should fail login with invalid mfa") { - await waitUntil(timeout: Timeout) { done in + waitUntil(timeout: Timeout) { done in auth.login(withOTP: OTP, mfaToken: "bad_token").start { result in expect(result).to(haveAuthenticationError(code: "invalid_grant", description: "Malformed mfa_token")) done() @@ -96,45 +96,49 @@ class AuthenticationSpec: QuickSpec { } } } - + describe("login MFA OOB") { - + beforeEach { - stub(condition: isToken(Domain) && hasAtLeast(["oob_code": OOB, "mfa_token": MFAToken])) { _ in return authResponse(accessToken: AccessToken, idToken: IdToken) }.name = "OpenID Auth" - stub(condition: isToken(Domain) && hasAtLeast(["oob_code": OOB, "mfa_token": MFAToken, "binding_code": BindingCode])) { _ in return authResponse(accessToken: AccessToken, idToken: IdToken) }.name = "OpenID Auth" - stub(condition: isToken(Domain) && hasAtLeast(["oob_code": "bad_oob", "mfa_token": MFAToken])) { _ in return authFailure(code: "invalid_grant", description: "Invalid oob_code.") }.name = "invalid oob_code" - stub(condition: isToken(Domain) && hasAtLeast(["oob_code": OOB, "mfa_token": "bad_token"])) { _ in return authFailure(code: "invalid_grant", description: "Malformed mfa_token") }.name = "invalid mfa_token" + NetworkStub.addStub(condition: { $0.isToken(Domain) && $0.hasAtLeast(["oob_code": OOB, "mfa_token": MFAToken]) + }, response: authResponse(accessToken: AccessToken, idToken: IdToken)) + NetworkStub.addStub(condition: { $0.isToken(Domain) && $0.hasAtLeast(["oob_code": OOB, "mfa_token": MFAToken, "binding_code": BindingCode]) + }, response: authResponse(accessToken: AccessToken, idToken: IdToken)) + NetworkStub.addStub(condition: { $0.isToken(Domain) && $0.hasAtLeast(["oob_code": "bad_oob", "mfa_token": MFAToken]) + }, response: authFailure(code: "invalid_grant", description: "Invalid oob_code.")) + NetworkStub.addStub(condition: { $0.isToken(Domain) && $0.hasAtLeast(["oob_code": OOB, "mfa_token": "bad_token"]) + }, response: authFailure(code: "invalid_grant", description: "Malformed mfa_token")) } - + it("should login with oob code and mfa tokens with default parameters") { - await waitUntil(timeout: Timeout) { done in + waitUntil(timeout: Timeout) { done in auth.login(withOOBCode: OOB, mfaToken: MFAToken).start { result in expect(result).to(haveCredentials()) done() } } } - + it("should login with oob code and mfa tokens with binding code") { - await waitUntil(timeout: Timeout) { done in + waitUntil(timeout: Timeout) { done in auth.login(withOOBCode: OOB, mfaToken: MFAToken, bindingCode: BindingCode).start { result in expect(result).to(haveCredentials()) done() } } } - + it("should fail login with bad oob code") { - await waitUntil(timeout: Timeout) { done in + waitUntil(timeout: Timeout) { done in auth.login(withOOBCode: "bad_oob", mfaToken: MFAToken, bindingCode: nil).start { result in expect(result).to(haveAuthenticationError(code: "invalid_grant", description: "Invalid oob_code.")) done() } } } - + it("should fail login with invalid mfa") { - await waitUntil(timeout: Timeout) { done in + waitUntil(timeout: Timeout) { done in auth.login(withOOBCode: OOB, mfaToken: "bad_token", bindingCode: nil).start { result in expect(result).to(haveAuthenticationError(code: "invalid_grant", description: "Malformed mfa_token")) done() @@ -142,35 +146,35 @@ class AuthenticationSpec: QuickSpec { } } } - + describe("login MFA recovery code") { - + beforeEach { - stub(condition: isToken(Domain) && hasAtLeast(["recovery_code": RecoveryCode, "mfa_token": MFAToken])) { _ in return authResponse(accessToken: AccessToken, idToken: IdToken) }.name = "OpenID Auth" - stub(condition: isToken(Domain) && hasAtLeast(["recovery_code": "bad_recovery", "mfa_token": MFAToken])) { _ in return authFailure(code: "invalid_grant", description: "Invalid recovery_code.") }.name = "invalid recovery code" - stub(condition: isToken(Domain) && hasAtLeast(["recovery_code": RecoveryCode, "mfa_token": "bad_token"])) { _ in return authFailure(code: "invalid_grant", description: "Malformed mfa_token") }.name = "invalid mfa_token" + NetworkStub.addStub(condition: { $0.isToken(Domain) && $0.hasAtLeast(["recovery_code": RecoveryCode, "mfa_token": MFAToken]) }, response: authResponse(accessToken: AccessToken, idToken: IdToken)) + NetworkStub.addStub(condition: { $0.isToken(Domain) && $0.hasAtLeast(["recovery_code": "bad_recovery", "mfa_token": MFAToken]) }, response: authFailure(code: "invalid_grant", description: "Invalid recovery_code.")) + NetworkStub.addStub(condition: { $0.isToken(Domain) && $0.hasAtLeast(["recovery_code": RecoveryCode, "mfa_token": "bad_token"]) }, response: authFailure(code: "invalid_grant", description: "Malformed mfa_token")) } - + it("should login with recovery code and mfa tokens") { - await waitUntil(timeout: Timeout) { done in + waitUntil(timeout: Timeout) { done in auth.login(withRecoveryCode: RecoveryCode, mfaToken: MFAToken).start { result in expect(result).to(haveCredentials()) done() } } } - + it("should fail login with bad recovery code") { - await waitUntil(timeout: Timeout) { done in + waitUntil(timeout: Timeout) { done in auth.login(withRecoveryCode: "bad_recovery", mfaToken: MFAToken).start { result in expect(result).to(haveAuthenticationError(code: "invalid_grant", description: "Invalid recovery_code.")) done() } } } - + it("should fail login with invalid mfa") { - await waitUntil(timeout: Timeout) { done in + waitUntil(timeout: Timeout) { done in auth.login(withRecoveryCode: RecoveryCode, mfaToken: "bad_token").start { result in expect(result).to(haveAuthenticationError(code: "invalid_grant", description: "Malformed mfa_token")) done() @@ -178,63 +182,74 @@ class AuthenticationSpec: QuickSpec { } } } - + // MARK:- MFA Challenge - + describe("MFA challenge") { - + beforeEach { - stub(condition: isMultifactorChallenge(Domain) && hasAtLeast([ - "mfa_token": MFAToken, - "client_id": ClientId - ])) { _ in return multifactorChallengeResponse(challengeType: "oob") }.name = "MFA Challenge" - stub(condition: isMultifactorChallenge(Domain) && hasAtLeast([ - "mfa_token": MFAToken, - "client_id": ClientId, - "challenge_type": "oob otp" - ])) { _ in return multifactorChallengeResponse(challengeType: "oob") }.name = "MFA Challenge" - stub(condition: isMultifactorChallenge(Domain) && hasAtLeast([ - "mfa_token": MFAToken, - "client_id": ClientId, - "authenticator_id": AuthenticatorId - ])) { _ in return multifactorChallengeResponse(challengeType: "oob") }.name = "MFA Challenge" - stub(condition: isMultifactorChallenge(Domain) && hasAtLeast([ - "mfa_token": MFAToken, - "client_id": ClientId, - "challenge_type": "oob otp", - "authenticator_id": AuthenticatorId - ])) { _ in return multifactorChallengeResponse(challengeType: "oob") }.name = "MFA Challenge" + NetworkStub.addStub(condition: { + $0.isMultifactorChallenge(Domain) && $0.hasAtLeast([ + "mfa_token": MFAToken, + "client_id": ClientId + ]) + }, response: multifactorChallengeResponse(challengeType: "oob")) + + NetworkStub.addStub(condition: { + $0.isMultifactorChallenge(Domain) && $0.hasAtLeast([ + "mfa_token": MFAToken, + "client_id": ClientId, + "challenge_type": "oob otp" + ]) + }, response: multifactorChallengeResponse(challengeType: "oob")) + + NetworkStub.addStub(condition: { + $0.isMultifactorChallenge(Domain) && $0.hasAtLeast([ + "mfa_token": MFAToken, + "client_id": ClientId, + "authenticator_id": AuthenticatorId + ]) + }, response: multifactorChallengeResponse(challengeType: "oob")) + + NetworkStub.addStub(condition: { + $0.isMultifactorChallenge(Domain) && $0.hasAtLeast([ + "mfa_token": MFAToken, + "client_id": ClientId, + "challenge_type": "oob otp", + "authenticator_id": AuthenticatorId + ]) + }, response: multifactorChallengeResponse(challengeType: "oob")) } - + it("should request MFA challenge with default parameters") { - await waitUntil(timeout: Timeout) { done in + waitUntil(timeout: Timeout) { done in auth.multifactorChallenge(mfaToken: MFAToken).start { result in expect(result).to(beSuccessful()) done() } } } - + it("should request MFA challenge with challenge types") { - await waitUntil(timeout: Timeout) { done in + waitUntil(timeout: Timeout) { done in auth.multifactorChallenge(mfaToken: MFAToken, types: ChallengeTypes).start { result in expect(result).to(beSuccessful()) done() } } } - + it("should request MFA challenge with authenticator id") { - await waitUntil(timeout: Timeout) { done in + waitUntil(timeout: Timeout) { done in auth.multifactorChallenge(mfaToken: MFAToken, authenticatorId: AuthenticatorId).start { result in expect(result).to(beSuccessful()) done() } } } - + it("should request MFA challenge with all parameters") { - await waitUntil(timeout: Timeout) { done in + waitUntil(timeout: Timeout) { done in auth.multifactorChallenge(mfaToken: MFAToken, types: ChallengeTypes, authenticatorId: AuthenticatorId).start { result in expect(result).to(beSuccessful()) done() @@ -242,67 +257,72 @@ class AuthenticationSpec: QuickSpec { } } } - + // MARK:- Refresh Tokens - + describe("renew auth with refresh token") { - + let refreshToken = UUID().uuidString.replacingOccurrences(of: "-", with: "") - + beforeEach { - stub(condition: isToken(Domain) && hasAtLeast(["refresh_token": refreshToken])) { _ in return authResponse(accessToken: AccessToken) }.name = "refresh_token login" + NetworkStub.addStub(condition: { + $0.isToken(Domain) && $0.hasAtLeast(["refresh_token": refreshToken]) + }, response: authResponse(accessToken: AccessToken)) } - + it("should receive access token") { - await waitUntil(timeout: Timeout) { done in + waitUntil(timeout: Timeout) { done in auth.renew(withRefreshToken: refreshToken).start { result in expect(result).to(haveCredentials()) done() } } } - + it("should receive access token sending scope") { - HTTPStubs.removeAllStubs() - stub(condition: isToken(Domain) && hasAtLeast(["refresh_token": refreshToken, "scope": "openid email"])) { _ in return authResponse(accessToken: AccessToken) } - await waitUntil(timeout: Timeout) { done in + NetworkStub.clearStubs() + NetworkStub.addStub(condition: { + $0.isToken(Domain) && $0.hasAtLeast(["refresh_token": refreshToken, "scope": "openid email"]) + }, response: authResponse(accessToken: AccessToken)) + waitUntil(timeout: Timeout) { done in auth.renew(withRefreshToken: refreshToken, scope: "openid email").start { result in expect(result).to(haveCredentials()) done() } } } - + it("should receive access token sending scope without enforcing openid scope") { - HTTPStubs.removeAllStubs() - stub(condition: isToken(Domain) && hasAtLeast(["refresh_token": refreshToken, "scope": "email phone"])) { _ in return authResponse(accessToken: AccessToken) } - await waitUntil(timeout: Timeout) { done in + NetworkStub.clearStubs() + NetworkStub.addStub(condition: { + $0.isToken(Domain) && $0.hasAtLeast(["refresh_token": refreshToken, "scope": "email phone"]) }, response: authResponse(accessToken: AccessToken)) + waitUntil(timeout: Timeout) { done in auth.renew(withRefreshToken: refreshToken, scope: "email phone").start { result in expect(result).to(haveCredentials()) done() } } } - - it("should fail to receive access token") { - let invalidRefreshToken = "invalidtoken" - - stub(condition: isToken(Domain) && hasAtLeast(["refresh_token": invalidRefreshToken])) { _ in - return authFailure(error: "", description: "") - }.name = "refresh_token login" - - await waitUntil(timeout: Timeout) { done in - auth.renew(withRefreshToken: invalidRefreshToken).start { result in - expect(result).toNot(haveCredentials()) - done() - } + } + + it("should fail to receive access token") { + let invalidRefreshToken = "invalidtoken" + + NetworkStub.addStub(condition: { + $0.isToken(Domain) && $0.hasAtLeast(["refresh_token": invalidRefreshToken]) + }, response: authFailure(error: "", description: "")) + + waitUntil(timeout: Timeout) { done in + auth.renew(withRefreshToken: invalidRefreshToken).start { result in + expect(result).toNot(haveCredentials()) + done() } } - } - + + // MARK:- Token Exchange - + describe("native social token exchange") { let validCode = "VALIDCODE" @@ -314,126 +334,129 @@ class AuthenticationSpec: QuickSpec { context("apple") { beforeEach { - stub(condition: isToken(Domain) && hasAllOf([ - "grant_type": TokenExchangeGrantType, - "subject_token": validCode, - "subject_token_type": "http://auth0.com/oauth/token-type/apple-authz-code", - "scope": defaultScope, - "client_id": ClientId - ])) { _ in return authResponse(accessToken: AccessToken, idToken: IdToken) }.name = "Token Exchange Apple Success" + NetworkStub.addStub(condition: { + $0.isToken(Domain) && $0.hasAllOf([ + "grant_type": TokenExchangeGrantType, + "subject_token": validCode, + "subject_token_type": "http://auth0.com/oauth/token-type/apple-authz-code", + "scope": defaultScope, + "client_id": ClientId + ]) + }, response: authResponse(accessToken: AccessToken, idToken: IdToken)) + + NetworkStub.addStub(condition: { + $0.isToken(Domain) && $0.hasAtLeast([ + "grant_type": TokenExchangeGrantType, + "subject_token": validCode, + "subject_token_type": "http://auth0.com/oauth/token-type/apple-authz-code", + "scope": "openid email", + ]) + }, response: authResponse(accessToken: AccessToken, idToken: IdToken)) - stub(condition: isToken(Domain) && hasAtLeast([ - "grant_type": TokenExchangeGrantType, - "subject_token": validCode, - "subject_token_type": "http://auth0.com/oauth/token-type/apple-authz-code", - "scope": "openid email" - ])) { _ in return authResponse(accessToken: AccessToken, idToken: IdToken) }.name = "Token Exchange Apple Success with custom scope" - - stub(condition: isToken(Domain) && hasAtLeast([ - "grant_type": TokenExchangeGrantType, - "subject_token": validCode, - "subject_token_type": "http://auth0.com/oauth/token-type/apple-authz-code", - "scope": "openid email phone" - ])) { _ in return authResponse(accessToken: AccessToken, idToken: IdToken) }.name = "Token Exchange Apple Success with custom scope enforcing openid scope" - - stub(condition: isToken(Domain) && hasAtLeast([ + NetworkStub.addStub(condition: { + $0.isToken(Domain) && $0.hasAtLeast([ + "grant_type": TokenExchangeGrantType, + "subject_token": validCode, + "subject_token_type": "http://auth0.com/oauth/token-type/apple-authz-code", + "scope": "openid email phone"])}, response: authResponse(accessToken: AccessToken, idToken: IdToken)) + + NetworkStub.addStub(condition: { $0.isToken(Domain) && $0.hasAtLeast([ "grant_type": TokenExchangeGrantType, "subject_token": validCode, "subject_token_type": "http://auth0.com/oauth/token-type/apple-authz-code", "scope": "openid email", "audience": "https://myapi.com/api" - ])) { _ in return authResponse(accessToken: AccessToken, idToken: IdToken) }.name = "Token Exchange Apple Success with custom scope and audience" + ])}, response: authResponse(accessToken: AccessToken, idToken: IdToken)) - stub(condition: isToken(Domain) && hasAtLeast([ + NetworkStub.addStub(condition: { $0.isToken(Domain) && $0.hasAtLeast([ "grant_type": TokenExchangeGrantType, "subject_token": validNameCode, - "subject_token_type": "http://auth0.com/oauth/token-type/apple-authz-code"]) && - (hasAtLeast(["user_profile": "{\"name\":{\"lastName\":\"Smith\",\"firstName\":\"John\"}}" ]) || hasAtLeast(["user_profile": "{\"name\":{\"firstName\":\"John\",\"lastName\":\"Smith\"}}" ])) - ) { _ in return authResponse(accessToken: AccessToken, idToken: IdToken) }.name = "Token Exchange Apple Success with user profile" + "subject_token_type": "http://auth0.com/oauth/token-type/apple-authz-code"]) && ($0.hasAtLeast(["user_profile": "{\"name\":{\"lastName\":\"Smith\",\"firstName\":\"John\"}}" ]) || $0.hasAtLeast(["user_profile": "{\"name\":{\"firstName\":\"John\",\"lastName\":\"Smith\"}}" ])) }, response: authResponse(accessToken: AccessToken, idToken: IdToken)) - stub(condition: isToken(Domain) && hasAtLeast([ + NetworkStub.addStub(condition: { $0.isToken(Domain) && $0.hasAtLeast([ "grant_type": TokenExchangeGrantType, "subject_token": validPartialNameCode, "subject_token_type": "http://auth0.com/oauth/token-type/apple-authz-code", "user_profile": "{\"name\":{\"firstName\":\"John\"}}" - ])) { _ in return authResponse(accessToken: AccessToken, idToken: IdToken) }.name = "Token Exchange Apple Success with partial user profile" + ])}, response: authResponse(accessToken: AccessToken, idToken: IdToken)) - stub(condition: isToken(Domain) && hasAtLeast([ + NetworkStub.addStub(condition: { $0.isToken(Domain) && $0.hasAtLeast([ "grant_type": TokenExchangeGrantType, "subject_token": validMissingNameCode, - "subject_token_type": "http://auth0.com/oauth/token-type/apple-authz-code"]) && - hasNoneOf(["user_profile"]) - ) { _ in return authResponse(accessToken: AccessToken, idToken: IdToken) }.name = "Token Exchange Apple Success with missing user profile" + "subject_token_type": "http://auth0.com/oauth/token-type/apple-authz-code"]) && + $0.hasNoneOf(["user_profile"]) + }, response: authResponse(accessToken: AccessToken, idToken: IdToken)) - stub(condition: isToken(Domain) && hasAtLeast([ + NetworkStub.addStub(condition: { $0.isToken(Domain) && $0.hasAtLeast([ "grant_type": TokenExchangeGrantType, "subject_token": validNameAndProfileCode, "subject_token_type": "http://auth0.com/oauth/token-type/apple-authz-code"]) && - (hasAtLeast(["user_profile": "{\"name\":{\"firstName\":\"John\"},\"user_metadata\":{\"custom_key\":\"custom_value\"}}"]) || hasAtLeast(["user_profile": "{\"user_metadata\":{\"custom_key\":\"custom_value\"},\"name\":{\"firstName\":\"John\"}}"])) - ) { _ in return authResponse(accessToken: AccessToken, idToken: IdToken) }.name = "Token Exchange Apple Success with user profile" - - stub(condition: isToken(Domain) && hasAllOf([ + ($0.hasAtLeast(["user_profile": "{\"name\":{\"firstName\":\"John\"},\"user_metadata\":{\"custom_key\":\"custom_value\"}}"]) || $0.hasAtLeast(["user_profile": "{\"user_metadata\":{\"custom_key\":\"custom_value\"},\"name\":{\"firstName\":\"John\"}}"]))}, + response: authResponse(accessToken: AccessToken, idToken: IdToken)) + + NetworkStub.addStub(condition: { $0.isToken(Domain) && $0.hasAllOf([ "grant_type": TokenExchangeGrantType, "subject_token": invalidCode, "subject_token_type": "http://auth0.com/oauth/token-type/apple-authz-code", "scope": defaultScope, "client_id": ClientId - ])) { _ in return authFailure(error: "", description: "") }.name = "Token Exchange Apple Failure" + ])}, response: authFailure(error: "", description: "") + ) } - + it("should exchange apple auth code for credentials") { - await waitUntil(timeout: Timeout) { done in + waitUntil(timeout: Timeout) { done in auth.login(appleAuthorizationCode: validCode) .start { result in expect(result).to(haveCredentials()) done() - } + } } - + } it("should exchange apple auth code and fail") { - await waitUntil(timeout: Timeout) { done in + waitUntil(timeout: Timeout) { done in auth.login(appleAuthorizationCode: invalidCode) .start { result in expect(result).toNot(haveCredentials()) done() - } + } } - + } it("should exchange apple auth code for credentials with custom scope") { - await waitUntil(timeout: Timeout) { done in + waitUntil(timeout: Timeout) { done in auth.login(appleAuthorizationCode: validCode, scope: "openid email") .start { result in expect(result).to(haveCredentials()) done() - } + } } - + } it("should exchange apple auth code for credentials with custom scope enforcing openid scope") { - await waitUntil(timeout: Timeout) { done in + waitUntil(timeout: Timeout) { done in auth.login(appleAuthorizationCode: validCode, scope: "email phone") .start { result in expect(result).to(haveCredentials()) done() - } + } } - + } it("should exchange apple auth code for credentials with custom scope and audience") { - await waitUntil(timeout: Timeout) { done in + waitUntil(timeout: Timeout) { done in auth.login(appleAuthorizationCode: validCode, audience: "https://myapi.com/api", scope: "openid email") .start { result in expect(result).to(haveCredentials()) done() - } + } } - + } it("should exchange apple auth code for credentials with fullName") { @@ -441,13 +464,13 @@ class AuthenticationSpec: QuickSpec { fullName.givenName = "John" fullName.familyName = "Smith" fullName.middleName = "Ignored" - - await waitUntil(timeout: Timeout) { done in + + waitUntil(timeout: Timeout) { done in auth.login(appleAuthorizationCode: validNameCode, fullName: fullName) .start { result in expect(result).to(haveCredentials()) done() - } + } } } @@ -457,12 +480,12 @@ class AuthenticationSpec: QuickSpec { fullName.familyName = nil fullName.middleName = "Ignored" - await waitUntil(timeout: Timeout) { done in + waitUntil(timeout: Timeout) { done in auth.login(appleAuthorizationCode: validPartialNameCode, fullName: fullName) .start { result in expect(result).to(haveCredentials()) done() - } + } } } @@ -472,241 +495,228 @@ class AuthenticationSpec: QuickSpec { fullName.familyName = nil fullName.middleName = nil - await waitUntil(timeout: Timeout) { done in + waitUntil(timeout: Timeout) { done in auth.login(appleAuthorizationCode: validMissingNameCode, fullName: fullName) .start { result in expect(result).to(haveCredentials()) done() - } + } } } - + it("should exchange apple auth code for credentials with fullName and profile") { var fullName = PersonNameComponents() fullName.givenName = "John" fullName.familyName = nil fullName.middleName = "Ignored" let profile = ["user_metadata": ["custom_key": "custom_value"]] - - await waitUntil(timeout: Timeout) { done in + + waitUntil(timeout: Timeout) { done in auth.login(appleAuthorizationCode: validNameAndProfileCode, fullName: fullName, profile: profile) - .start { result in - expect(result).to(haveCredentials()) - done() + .start { result in + expect(result).to(haveCredentials()) + done() } } } } - + context("facebook") { let sessionAccessToken = UUID().uuidString.replacingOccurrences(of: "-", with: "") let profile = ["name": "John Smith"] - + it("should exchage the session access token and profile data for credentials") { - stub(condition: isToken(Domain) && hasAllOf([ + NetworkStub.addStub(condition: { $0.isToken(Domain) && $0.hasAllOf([ "grant_type": TokenExchangeGrantType, "subject_token": sessionAccessToken, "subject_token_type": "http://auth0.com/oauth/token-type/facebook-info-session-access-token", "scope": defaultScope, "user_profile": "{\"name\":\"John Smith\"}", "client_id": ClientId - ])) { _ in - return authResponse(accessToken: AccessToken, idToken: IdToken) - } - - await waitUntil(timeout: Timeout) { done in + ])}, response: authResponse(accessToken: AccessToken, idToken: IdToken)) + + waitUntil(timeout: Timeout) { done in auth.login(facebookSessionAccessToken: sessionAccessToken, profile: profile) .start { result in expect(result).to(haveCredentials(AccessToken, IdToken)) done() - } + } } } - + it("should include profile data") { - stub(condition: isToken(Domain) && - (hasAtLeast(["user_profile": "{\"name\":\"John Smith\",\"email\":\"john@smith.com\"}" ]) || - hasAtLeast(["user_profile": "{\"email\":\"john@smith.com\",\"name\":\"John Smith\"}" ]))) { _ in - return authResponse(accessToken: AccessToken, idToken: IdToken) - } - - await waitUntil(timeout: Timeout) { done in + NetworkStub.addStub(condition: { $0.isToken(Domain) && + ($0.hasAtLeast(["user_profile": "{\"name\":\"John Smith\",\"email\":\"john@smith.com\"}" ]) || + $0.hasAtLeast(["user_profile": "{\"email\":\"john@smith.com\",\"name\":\"John Smith\"}" ]))}, response: authResponse(accessToken: AccessToken, idToken: IdToken)) + + waitUntil(timeout: Timeout) { done in auth.login(facebookSessionAccessToken: sessionAccessToken, profile: ["name": "John Smith", "email": "john@smith.com"]) - .start { result in - expect(result).to(haveCredentials(AccessToken, IdToken)) - done() + .start { result in + expect(result).to(haveCredentials(AccessToken, IdToken)) + done() } } } - + it("should include custom scope") { - stub(condition: isToken(Domain) && hasAtLeast(["scope": "openid email"])) { _ in - return authResponse(accessToken: AccessToken, idToken: IdToken) - } - - await waitUntil(timeout: Timeout) { done in + NetworkStub.addStub(condition: { $0.isToken(Domain) && $0.hasAtLeast(["scope": "openid email"])}, response: authResponse(accessToken: AccessToken, idToken: IdToken)) + + waitUntil(timeout: Timeout) { done in auth.login(facebookSessionAccessToken: sessionAccessToken, profile: profile, scope: "openid email") - .start { result in - expect(result).to(haveCredentials(AccessToken, IdToken)) - done() + .start { result in + expect(result).to(haveCredentials(AccessToken, IdToken)) + done() } } } - + it("should include custom scope enforcing openid scope") { - stub(condition: isToken(Domain) && hasAtLeast(["scope": "openid email phone"])) { _ in - return authResponse(accessToken: AccessToken, idToken: IdToken) - } - - await waitUntil(timeout: Timeout) { done in + NetworkStub.addStub(condition: { $0.isToken(Domain) && $0.hasAtLeast(["scope": "openid email phone"]) }, response: authResponse(accessToken: AccessToken, idToken: IdToken)) + + waitUntil(timeout: Timeout) { done in auth.login(facebookSessionAccessToken: sessionAccessToken, profile: profile, scope: "email phone") - .start { result in - expect(result).to(haveCredentials(AccessToken, IdToken)) - done() + .start { result in + expect(result).to(haveCredentials(AccessToken, IdToken)) + done() } } } - + it("should include audience if it is not nil") { - stub(condition: isToken(Domain) && hasAtLeast(["audience": "https://myapi.com/api"])) { _ in - return authResponse(accessToken: AccessToken, idToken: IdToken) - } - - await waitUntil(timeout: Timeout) { done in + NetworkStub.addStub(condition: { $0.isToken(Domain) && $0.hasAtLeast(["audience": "https://myapi.com/api"]) }, response: authResponse(accessToken: AccessToken, idToken: IdToken)) + + waitUntil(timeout: Timeout) { done in auth.login(facebookSessionAccessToken: sessionAccessToken, profile: profile, audience: "https://myapi.com/api") - .start { result in - expect(result).to(haveCredentials(AccessToken, IdToken)) - done() + .start { result in + expect(result).to(haveCredentials(AccessToken, IdToken)) + done() } } } } - + } - + describe("revoke refresh token") { - + let refreshToken = UUID().uuidString.replacingOccurrences(of: "-", with: "") - + it("should revoke token") { - stub(condition: isRevokeToken(Domain) && hasAtLeast(["token": refreshToken])) { _ in - return revokeTokenResponse() }.name = "revokeToken" - await waitUntil(timeout: Timeout) { done in + NetworkStub.addStub(condition: { $0.isRevokeToken(Domain) && $0.hasAtLeast(["token": refreshToken]) }, response: revokeTokenResponse()) + waitUntil(timeout: Timeout) { done in auth.revoke(refreshToken: refreshToken).start { result in guard case .success = result else { return fail("Failed to revoke token") } done() } } } - + it("should fail to revoke token") { let code = "invalid_request" let description = "missing params" - stub(condition: isRevokeToken(Domain) && hasAtLeast(["token": refreshToken])) { _ in - return authFailure(code: code, description: description) }.name = "revoke failed" - await waitUntil(timeout: Timeout) { done in + NetworkStub.addStub(condition: { $0.isRevokeToken(Domain) && $0.hasAtLeast(["token": refreshToken]) }, response: authFailure(code: code, description: description)) + waitUntil(timeout: Timeout) { done in auth.revoke(refreshToken: refreshToken).start { result in expect(result).to(haveAuthenticationError(code: code, description: description)) done() } } } - + } - + // MARK:- password-realm grant type - + describe("authenticating with credentials and a realm/connection") { - + it("should receive token with username and password") { - stub(condition: isToken(Domain) && hasAtLeast(["username": SupportAtAuth0, "password": ValidPassword, "realm": "myrealm"])) { _ in return authResponse(accessToken: AccessToken) }.name = "Grant Password" - await waitUntil(timeout: Timeout) { done in + NetworkStub.addStub(condition: { $0.isToken(Domain) && $0.hasAtLeast(["username": SupportAtAuth0, "password": ValidPassword, "realm": "myrealm"])} , response: authResponse(accessToken: AccessToken)) + waitUntil(timeout: Timeout) { done in auth.login(usernameOrEmail: SupportAtAuth0, password: ValidPassword, realmOrConnection: "myrealm").start { result in expect(result).to(haveCredentials()) done() } } } - + it("should fail to return token") { - stub(condition: isToken(Domain) && hasAtLeast(["username": SupportAtAuth0, "password": InvalidPassword, "realm": "myrealm"])) { _ in return authFailure(error: "", description: "") }.name = "Grant Password" - await waitUntil(timeout: Timeout) { done in + NetworkStub.addStub(condition: { $0.isToken(Domain) && $0.hasAtLeast(["username": SupportAtAuth0, "password": InvalidPassword, "realm": "myrealm"])} , response: authFailure(error: "", description: "")) + waitUntil(timeout: Timeout) { done in auth.login(usernameOrEmail: SupportAtAuth0, password: InvalidPassword, realmOrConnection: "myrealm").start { result in expect(result).toNot(haveCredentials()) done() } } } - + it("should specify scope in request") { - stub(condition: isToken(Domain) && hasAtLeast(["username": SupportAtAuth0, "password": ValidPassword, "scope": "openid", "realm": "myrealm"])) { _ in return authResponse(accessToken: AccessToken) }.name = "Grant Password Custom Scope" - await waitUntil(timeout: Timeout) { done in + NetworkStub.addStub(condition: { $0.isToken(Domain) && $0.hasAtLeast(["username": SupportAtAuth0, "password": ValidPassword, "scope": "openid", "realm": "myrealm"])} , response: authResponse(accessToken: AccessToken)) + waitUntil(timeout: Timeout) { done in auth.login(usernameOrEmail: SupportAtAuth0, password: ValidPassword, realmOrConnection: "myrealm", scope: "openid").start { result in expect(result).to(haveCredentials()) done() } } } - + it("should specify scope in request enforcing openid scope") { - stub(condition: isToken(Domain) && hasAtLeast(["username": SupportAtAuth0, "password": ValidPassword, "scope": "openid email phone", "realm": "myrealm"])) { _ in return authResponse(accessToken: AccessToken) }.name = "Grant Password Custom Scope" - await waitUntil(timeout: Timeout) { done in + NetworkStub.addStub(condition: { $0.isToken(Domain) && $0.hasAtLeast(["username": SupportAtAuth0, "password": ValidPassword, "scope": "openid email phone", "realm": "myrealm"])} , response: authResponse(accessToken: AccessToken)) + waitUntil(timeout: Timeout) { done in auth.login(usernameOrEmail: SupportAtAuth0, password: ValidPassword, realmOrConnection: "myrealm", scope: "email phone").start { result in expect(result).to(haveCredentials()) done() } } } - + it("should specify audience in request") { - stub(condition: isToken(Domain) && hasAtLeast(["username": SupportAtAuth0, "password": ValidPassword, "audience" : "https://myapi.com/api", "realm": "myrealm"])) { _ in return authResponse(accessToken: AccessToken) }.name = "Grant Password Custom Scope and audience" - await waitUntil(timeout: Timeout) { done in + NetworkStub.addStub(condition: { $0.isToken(Domain) && $0.hasAtLeast(["username": SupportAtAuth0, "password": ValidPassword, "audience" : "https://myapi.com/api", "realm": "myrealm"])} , response: authResponse(accessToken: AccessToken)) + waitUntil(timeout: Timeout) { done in auth.login(usernameOrEmail: SupportAtAuth0, password: ValidPassword, realmOrConnection: "myrealm", audience: "https://myapi.com/api").start { result in expect(result).to(haveCredentials()) done() } } } - + it("should specify audience and scope in request") { - stub(condition: isToken(Domain) && hasAtLeast(["username": SupportAtAuth0, "password": ValidPassword, "scope": "openid", "audience" : "https://myapi.com/api", "realm": "myrealm"])) { _ in return authResponse(accessToken: AccessToken) }.name = "Grant Password Custom Scope and audience" - await waitUntil(timeout: Timeout) { done in + NetworkStub.addStub(condition: { $0.isToken(Domain) && $0.hasAtLeast(["username": SupportAtAuth0, "password": ValidPassword, "audience" : "https://myapi.com/api", "scope": "openid", "realm": "myrealm"])} , response: authResponse(accessToken: AccessToken)) + waitUntil(timeout: Timeout) { done in auth.login(usernameOrEmail: SupportAtAuth0, password: ValidPassword, realmOrConnection: "myrealm", audience: "https://myapi.com/api", scope: "openid").start { result in expect(result).to(haveCredentials()) done() } } } - + it("should specify audience, scope and realm/connection in request") { - stub(condition: isToken(Domain) && hasAtLeast(["username": SupportAtAuth0, "password": ValidPassword, "scope": "openid", "audience" : "https://myapi.com/api", "realm" : "customconnection"])) { _ in return authResponse(accessToken: AccessToken) }.name = "Grant Password Custom audience, scope and realm" - await waitUntil(timeout: Timeout) { done in + NetworkStub.addStub(condition: { $0.isToken(Domain) && $0.hasAtLeast(["username": SupportAtAuth0, "password": ValidPassword, "audience" : "https://myapi.com/api", "scope": "openid", "realm": "customconnection"])} , response: authResponse(accessToken: AccessToken)) + waitUntil(timeout: Timeout) { done in auth.login(usernameOrEmail: SupportAtAuth0, password: ValidPassword, realmOrConnection: "customconnection", audience: "https://myapi.com/api", scope: "openid").start { result in expect(result).to(haveCredentials()) done() } } } - + } // MARK:- password grant type - + describe("authenticating with credentials in a default directory") { it("should receive token with username and password") { - stub(condition: isToken(Domain) && hasAtLeast(["username":SupportAtAuth0, "password": ValidPassword])) { _ in return authResponse(accessToken: AccessToken) }.name = "Grant Password" - - await waitUntil(timeout: Timeout) { done in + NetworkStub.addStub(condition: { $0.isToken(Domain) && $0.hasAtLeast(["username": SupportAtAuth0, "password": ValidPassword])} , response: authResponse(accessToken: AccessToken)) + waitUntil(timeout: Timeout) { done in auth.loginDefaultDirectory(withUsername: SupportAtAuth0, password: ValidPassword).start { result in expect(result).to(haveCredentials()) done() @@ -715,8 +725,8 @@ class AuthenticationSpec: QuickSpec { } it("should fail to return token") { - stub(condition: isToken(Domain) && hasAtLeast(["username": SupportAtAuth0, "password": InvalidPassword])) { _ in return authFailure(error: "", description: "") }.name = "Grant Password" - await waitUntil(timeout: Timeout) { done in + NetworkStub.addStub(condition: { $0.isToken(Domain) && $0.hasAtLeast(["username": SupportAtAuth0, "password": InvalidPassword])} , response: authFailure(error: "", description: "")) + waitUntil(timeout: Timeout) { done in auth.loginDefaultDirectory(withUsername: SupportAtAuth0, password: InvalidPassword).start { result in expect(result).toNot(haveCredentials()) done() @@ -725,8 +735,8 @@ class AuthenticationSpec: QuickSpec { } it("should specify scope in request") { - stub(condition: isToken(Domain) && hasAtLeast(["username": SupportAtAuth0, "password": ValidPassword, "scope": "openid"])) { _ in return authResponse(accessToken: AccessToken) }.name = "Grant Password Custom Scope" - await waitUntil(timeout: Timeout) { done in + NetworkStub.addStub(condition: { $0.isToken(Domain) && $0.hasAtLeast(["username": SupportAtAuth0, "password": ValidPassword, "scope": "openid"])} , response: authResponse(accessToken: AccessToken)) + waitUntil(timeout: Timeout) { done in auth.loginDefaultDirectory(withUsername: SupportAtAuth0, password: ValidPassword, scope: "openid").start { result in expect(result).to(haveCredentials()) done() @@ -735,8 +745,8 @@ class AuthenticationSpec: QuickSpec { } it("should specify scope in request enforcing openid scope") { - stub(condition: isToken(Domain) && hasAtLeast(["username": SupportAtAuth0, "password": ValidPassword, "scope": "openid email phone"])) { _ in return authResponse(accessToken: AccessToken) }.name = "Grant Password Custom Scope" - await waitUntil(timeout: Timeout) { done in + NetworkStub.addStub(condition: { $0.isToken(Domain) && $0.hasAtLeast(["username": SupportAtAuth0, "password": ValidPassword, "scope": "openid email phone"])} , response: authResponse(accessToken: AccessToken)) + waitUntil(timeout: Timeout) { done in auth.loginDefaultDirectory(withUsername: SupportAtAuth0, password: ValidPassword, scope: "email phone").start { result in expect(result).to(haveCredentials()) done() @@ -745,8 +755,8 @@ class AuthenticationSpec: QuickSpec { } it("should specify audience in request") { - stub(condition: isToken(Domain) && hasAtLeast(["username": SupportAtAuth0, "password": ValidPassword, "audience" : "https://myapi.com/api"])) { _ in return authResponse(accessToken: AccessToken) }.name = "Grant Password Custom Audience" - await waitUntil(timeout: Timeout) { done in + NetworkStub.addStub(condition: { $0.isToken(Domain) && $0.hasAtLeast(["username": SupportAtAuth0, "password": ValidPassword, "audience" : "https://myapi.com/api"])} , response: authResponse(accessToken: AccessToken)) + waitUntil(timeout: Timeout) { done in auth.loginDefaultDirectory(withUsername: SupportAtAuth0, password: ValidPassword, audience: "https://myapi.com/api").start { result in expect(result).to(haveCredentials()) done() @@ -755,61 +765,61 @@ class AuthenticationSpec: QuickSpec { } it("should specify audience and scope in request") { - stub(condition: isToken(Domain) && hasAtLeast(["username": SupportAtAuth0, "password": ValidPassword, "scope": "openid", "audience" : "https://myapi.com/api"])) { _ in return authResponse(accessToken: AccessToken) }.name = "Grant Password Custom Scope and audience" - await waitUntil(timeout: Timeout) { done in + NetworkStub.addStub(condition: { $0.isToken(Domain) && $0.hasAtLeast(["username": SupportAtAuth0, "password": ValidPassword, "scope": "openid", "audience" : "https://myapi.com/api"])} , response: authResponse(accessToken: AccessToken)) + waitUntil(timeout: Timeout) { done in auth.loginDefaultDirectory(withUsername: SupportAtAuth0, password: ValidPassword, audience: "https://myapi.com/api", scope: "openid").start { result in expect(result).to(haveCredentials()) done() } } } - + } - + describe("create user") { - + beforeEach { - stub(condition: isSignUp(Domain) && hasAllOf(["email": SupportAtAuth0, "password": ValidPassword, "connection": ConnectionName, "client_id": ClientId])) { _ in return createdUser(email: SupportAtAuth0) }.name = "User w/email" - stub(condition: isSignUp(Domain) && hasAllOf(["email": SupportAtAuth0, "username": Support, "password": ValidPassword, "connection": ConnectionName, "client_id": ClientId])) { _ in return createdUser(email: SupportAtAuth0, username: Support) }.name = "User w/username" + NetworkStub.addStub(condition: { $0.isSignUp(Domain) && $0.hasAllOf(["email": SupportAtAuth0, "password": ValidPassword, "connection": ConnectionName, "client_id": ClientId])}, response: createdUser(email: SupportAtAuth0)) + NetworkStub.addStub(condition: { $0.isSignUp(Domain) && $0.hasAllOf(["email": SupportAtAuth0, "username": Support, "password": ValidPassword, "connection": ConnectionName, "client_id": ClientId])}, response: createdUser(email: SupportAtAuth0, username: Support)) } - + it("should create a user with email & password") { - await waitUntil(timeout: Timeout) { done in + waitUntil(timeout: Timeout) { done in auth.signup(email: SupportAtAuth0, password: ValidPassword, connection: ConnectionName).start { result in expect(result).to(haveCreatedUser(SupportAtAuth0)) done() } } } - + it("should create a user with email, username & password") { - await waitUntil(timeout: Timeout) { done in + waitUntil(timeout: Timeout) { done in auth.signup(email: SupportAtAuth0, username: Support, password: ValidPassword, connection: ConnectionName).start { result in expect(result).to(haveCreatedUser(SupportAtAuth0, username: Support)) done() } } } - + it("should provide error payload from auth api") { - await waitUntil(timeout: Timeout) { done in + waitUntil(timeout: Timeout) { done in let code = "invalid_username_password" let description = "Invalid password" let password = "return invalid password" - stub(condition: isSignUp(Domain) && hasAtLeast(["password": password])) { _ in return authFailure(code: code, description: description) }.name = "invalid password" + NetworkStub.addStub(condition: { $0.isSignUp(Domain) && $0.hasAtLeast(["password": password])}, response: authFailure(code: code, description: description)) auth.signup(email: SupportAtAuth0, password: password, connection: ConnectionName).start { result in expect(result).to(haveAuthenticationError(code: code, description: description)) done() } } } - + it("should send user metadata") { let country = "Argentina" let email = "metadata@auth0.com" let metadata = ["country": country] - stub(condition: isSignUp(Domain) && hasUserMetadata(metadata)) { _ in return createdUser(email: email) }.name = "User w/metadata" - await waitUntil(timeout: Timeout) { done in + NetworkStub.addStub(condition: {$0.isSignUp(Domain) && $0.hasUserMetadata(metadata)}, response: createdUser(email: email)) + waitUntil(timeout: Timeout) { done in auth.signup(email: email, password: ValidPassword, connection: ConnectionName, userMetadata: metadata).start { result in expect(result).to(haveCreatedUser(email)) done() @@ -822,8 +832,8 @@ class AuthenticationSpec: QuickSpec { it("should send root attributes") { let attributes = ["family_name": "Doe", "nickname" : "Johnny"] - stub(condition: isSignUp(Domain) && hasAtLeast(attributes)) { _ in return createdUser(email: SupportAtAuth0) }.name = "User w/root attributes" - await waitUntil(timeout: Timeout) { done in + NetworkStub.addStub(condition: { $0.isSignUp(Domain) && $0.hasAtLeast(attributes)}, response:createdUser(email: SupportAtAuth0)) + waitUntil(timeout: Timeout) { done in auth.signup(email: SupportAtAuth0, password: ValidPassword, connection: ConnectionName, rootAttributes: attributes).start { result in expect(result).to(haveCreatedUser(SupportAtAuth0)) done() @@ -835,8 +845,8 @@ class AuthenticationSpec: QuickSpec { let attributes = ["family_name": "Doe", "nickname" : "Johnny", "email" : "root@email.com"] - stub(condition: isSignUp(Domain) && !hasAtLeast(attributes)) { _ in return createdUser(email: SupportAtAuth0) }.name = "User w/root attributes" - await waitUntil(timeout: Timeout) { done in + NetworkStub.addStub(condition: { $0.isSignUp(Domain) && !$0.hasAtLeast(attributes)}, response:createdUser(email: SupportAtAuth0)) + waitUntil(timeout: Timeout) { done in auth.signup(email: SupportAtAuth0, password: ValidPassword, connection: ConnectionName, rootAttributes: attributes).start { result in expect(result).to(haveCreatedUser(SupportAtAuth0)) done() @@ -845,60 +855,60 @@ class AuthenticationSpec: QuickSpec { } } - + } - + describe("reset password") { - + it("should reset password") { - stub(condition: isResetPassword(Domain) && hasAllOf(["email": SupportAtAuth0, "connection": ConnectionName, "client_id": ClientId])) { _ in return resetPasswordResponse() }.name = "reset request sent" - await waitUntil(timeout: Timeout) { done in + NetworkStub.addStub(condition: { $0.isResetPassword(Domain) && $0.hasAllOf(["email": SupportAtAuth0, "connection": ConnectionName, "client_id": ClientId])}, response: resetPasswordResponse()) + waitUntil(timeout: Timeout) { done in auth.resetPassword(email: SupportAtAuth0, connection: ConnectionName).start { result in guard case .success = result else { return fail("Failed to reset password") } done() } } } - + it("should fail to reset password") { let code = "reset_failed" let description = "failed reset password" - stub(condition: isResetPassword(Domain) && hasAllOf(["email": SupportAtAuth0, "connection": ConnectionName, "client_id": ClientId])) { _ in return authFailure(code: code, description: description) }.name = "reset failed" - await waitUntil(timeout: Timeout) { done in + NetworkStub.addStub(condition: { $0.isResetPassword(Domain) && $0.hasAllOf(["email": SupportAtAuth0, "connection": ConnectionName, "client_id": ClientId])}, response: authFailure(code: code, description: description)) + waitUntil(timeout: Timeout) { done in auth.resetPassword(email: SupportAtAuth0, connection: ConnectionName).start { result in expect(result).to(haveAuthenticationError(code: code, description: description)) done() } } } - + } - + describe("passwordless email") { - + it("should start with email with default values") { - stub(condition: isPasswordless(Domain) && hasAllOf(["email": SupportAtAuth0, "connection": "email", "client_id": ClientId, "send": "code"])) { _ in return passwordless(SupportAtAuth0, verified: true) }.name = "email passwordless" - await waitUntil(timeout: Timeout) { done in + NetworkStub.addStub(condition: { $0.isPasswordless(Domain) && $0.hasAllOf(["email": SupportAtAuth0, "connection": "email", "client_id": ClientId, "send": "code"])}, response: passwordless(SupportAtAuth0, verified: true)) + waitUntil(timeout: Timeout) { done in auth.startPasswordless(email: SupportAtAuth0).start { result in expect(result).to(beSuccessful()) done() } } } - + it("should start with email") { - stub(condition: isPasswordless(Domain) && hasAllOf(["email": SupportAtAuth0, "connection": "custom_email", "client_id": ClientId, "send": "link_ios"])) { _ in return passwordless(SupportAtAuth0, verified: true) }.name = "email passwordless custom" - await waitUntil(timeout: Timeout) { done in + NetworkStub.addStub(condition: { $0.isPasswordless(Domain) && $0.hasAllOf(["email": SupportAtAuth0, "connection": "custom_email", "client_id": ClientId, "send": "link_ios"])}, response: passwordless(SupportAtAuth0, verified: true)) + waitUntil(timeout: Timeout) { done in auth.startPasswordless(email: SupportAtAuth0, type: .iOSLink, connection: "custom_email").start { result in expect(result).to(beSuccessful()) done() } } } - + it("should fail to start") { - stub(condition: isPasswordless(Domain)) { _ in return authFailure(error: "error", description: "description") }.name = "failed passwordless start" - await waitUntil(timeout: Timeout) { done in + NetworkStub.addStub(condition: { $0.isPasswordless(Domain) }, response:authFailure(error: "error", description: "description")) + waitUntil(timeout: Timeout) { done in auth.startPasswordless(email: SupportAtAuth0).start { result in expect(result).to(haveAuthenticationError(code: "error", description: "description")) done() @@ -909,10 +919,8 @@ class AuthenticationSpec: QuickSpec { context("passwordless login") { it("should login with email code") { - stub(condition: isToken(Domain) && hasAtLeast(["username": SupportAtAuth0, "otp": OTP, "realm": "email", "scope": defaultScope, "grant_type": PasswordlessGrantType, "client_id": ClientId])) { _ in - return authResponse(accessToken: AccessToken) - } - await waitUntil(timeout: Timeout) { done in + NetworkStub.addStub(condition: { $0.isToken(Domain) && $0.hasAtLeast(["username": SupportAtAuth0, "otp": OTP, "realm": "email", "scope": defaultScope, "grant_type": PasswordlessGrantType, "client_id": ClientId])}, response: authResponse(accessToken: AccessToken)) + waitUntil(timeout: Timeout) { done in auth.login(email: SupportAtAuth0, code: OTP).start { result in expect(result).to(haveCredentials(AccessToken)) done() @@ -921,10 +929,8 @@ class AuthenticationSpec: QuickSpec { } it("should include custom scope") { - stub(condition: isToken(Domain) && hasAtLeast(["scope": "openid email"])) { _ in - return authResponse(accessToken: AccessToken) - } - await waitUntil(timeout: Timeout) { done in + NetworkStub.addStub(condition: { $0.isToken(Domain) && $0.hasAtLeast(["scope": "openid email"])}, response: authResponse(accessToken: AccessToken)) + waitUntil(timeout: Timeout) { done in auth.login(email: SupportAtAuth0, code: OTP, scope: "openid email").start { result in expect(result).to(beSuccessful()) done() @@ -933,10 +939,8 @@ class AuthenticationSpec: QuickSpec { } it("should include custom scope enforcing openid scope") { - stub(condition: isToken(Domain) && hasAtLeast(["scope": "openid email phone"])) { _ in - return authResponse(accessToken: AccessToken) - } - await waitUntil(timeout: Timeout) { done in + NetworkStub.addStub(condition: { $0.isToken(Domain) && $0.hasAtLeast(["scope": "openid email phone"])}, response: authResponse(accessToken: AccessToken)) + waitUntil(timeout: Timeout) { done in auth.login(email: SupportAtAuth0, code: OTP, scope: "email phone").start { result in expect(result).to(beSuccessful()) done() @@ -945,10 +949,8 @@ class AuthenticationSpec: QuickSpec { } it("should include audience if it is not nil") { - stub(condition: isToken(Domain) && hasAtLeast(["audience": "https://myapi.com/api"])) { _ in - return authResponse(accessToken: AccessToken) - } - await waitUntil(timeout: Timeout) { done in + NetworkStub.addStub(condition: { $0.isToken(Domain) && $0.hasAtLeast(["audience": "https://myapi.com/api"])}, response: authResponse(accessToken: AccessToken)) + waitUntil(timeout: Timeout) { done in auth.login(email: SupportAtAuth0, code: OTP, audience: "https://myapi.com/api").start { result in expect(result).to(beSuccessful()) done() @@ -957,10 +959,8 @@ class AuthenticationSpec: QuickSpec { } it("should not include audience if it is nil") { - stub(condition: isToken(Domain) && hasNoneOf(["audience"])) { _ in - return authResponse(accessToken: AccessToken) - } - await waitUntil(timeout: Timeout) { done in + NetworkStub.addStub(condition: { $0.isToken(Domain) && $0.hasNoneOf(["audience"])}, response: authResponse(accessToken: AccessToken)) + waitUntil(timeout: Timeout) { done in auth.login(email: SupportAtAuth0, code: OTP, audience: nil).start { result in expect(result).to(beSuccessful()) done() @@ -969,10 +969,8 @@ class AuthenticationSpec: QuickSpec { } it("should not include audience by default") { - stub(condition: isToken(Domain) && hasNoneOf(["audience"])) { _ in - return authResponse(accessToken: AccessToken) - } - await waitUntil(timeout: Timeout) { done in + NetworkStub.addStub(condition: { $0.isToken(Domain) && $0.hasNoneOf(["audience"])}, response: authResponse(accessToken: AccessToken)) + waitUntil(timeout: Timeout) { done in auth.login(email: SupportAtAuth0, code: OTP).start { result in expect(result).to(beSuccessful()) done() @@ -980,35 +978,35 @@ class AuthenticationSpec: QuickSpec { } } - + } } - + describe("passwordless sms") { - + it("should start with sms with default values") { - stub(condition: isPasswordless(Domain) && hasAllOf(["phone_number": Phone, "connection": "sms", "client_id": ClientId, "send": "code"])) { _ in return passwordless(SupportAtAuth0, verified: true) }.name = "sms passwordless" - await waitUntil(timeout: Timeout) { done in + NetworkStub.addStub(condition: { $0.isPasswordless(Domain) && $0.hasAllOf(["phone_number": Phone, "connection": "sms", "client_id": ClientId, "send": "code"]) }, response:passwordless(SupportAtAuth0, verified: true)) + waitUntil(timeout: Timeout) { done in auth.startPasswordless(phoneNumber: Phone).start { result in expect(result).to(beSuccessful()) done() } } } - + it("should start with sms") { - stub(condition: isPasswordless(Domain) && hasAllOf(["phone_number": Phone, "connection": "custom_sms", "client_id": ClientId, "send": "link_ios"])) { _ in return passwordless(SupportAtAuth0, verified: true) }.name = "sms passwordless custom" - await waitUntil(timeout: Timeout) { done in + NetworkStub.addStub(condition: { $0.isPasswordless(Domain) && $0.hasAllOf(["phone_number": Phone, "connection": "custom_sms", "client_id": ClientId, "send": "link_ios"]) }, response:passwordless(SupportAtAuth0, verified: true)) + waitUntil(timeout: Timeout) { done in auth.startPasswordless(phoneNumber: Phone, type: .iOSLink, connection: "custom_sms").start { result in expect(result).to(beSuccessful()) done() } } } - + it("should fail to start") { - stub(condition: isPasswordless(Domain)) { _ in return authFailure(error: "error", description: "description") }.name = "failed passwordless start" - await waitUntil(timeout: Timeout) { done in + NetworkStub.addStub(condition: { $0.isPasswordless(Domain)}, response: authFailure(error: "error", description: "description")) + waitUntil(timeout: Timeout) { done in auth.startPasswordless(phoneNumber: Phone).start { result in expect(result).to(haveAuthenticationError(code: "error", description: "description")) done() @@ -1021,10 +1019,8 @@ class AuthenticationSpec: QuickSpec { let smsRealm = "sms" it("should login with sms code") { - stub(condition: isToken(Domain) && hasAtLeast(["username": Phone, "otp": OTP, "realm": smsRealm, "scope": defaultScope, "grant_type": PasswordlessGrantType, "client_id": ClientId])) { _ in - return authResponse(accessToken: AccessToken) - } - await waitUntil(timeout: Timeout) { done in + NetworkStub.addStub(condition: { $0.isToken(Domain) && $0.hasAtLeast(["username": Phone, "otp": OTP, "realm": smsRealm, "scope": defaultScope, "grant_type": PasswordlessGrantType, "client_id": ClientId])}, response: authResponse(accessToken: AccessToken)) + waitUntil(timeout: Timeout) { done in auth.login(phoneNumber: Phone, code: OTP).start { result in expect(result).to(haveCredentials(AccessToken)) done() @@ -1033,10 +1029,8 @@ class AuthenticationSpec: QuickSpec { } it("should include custom scope") { - stub(condition: isToken(Domain) && hasAtLeast(["scope": "openid email"])) { _ in - return authResponse(accessToken: AccessToken) - } - await waitUntil(timeout: Timeout) { done in + NetworkStub.addStub(condition: { $0.isToken(Domain) && $0.hasAtLeast(["scope": "openid email"])}, response: authResponse(accessToken: AccessToken)) + waitUntil(timeout: Timeout) { done in auth.login(phoneNumber: Phone, code: OTP, scope: "openid email").start { result in expect(result).to(beSuccessful()) done() @@ -1045,10 +1039,8 @@ class AuthenticationSpec: QuickSpec { } it("should include custom scope enforcing openid scope") { - stub(condition: isToken(Domain) && hasAtLeast(["scope": "openid email phone"])) { _ in - return authResponse(accessToken: AccessToken) - } - await waitUntil(timeout: Timeout) { done in + NetworkStub.addStub(condition: { $0.isToken(Domain) && $0.hasAtLeast(["scope": "openid email phone"])}, response: authResponse(accessToken: AccessToken)) + waitUntil(timeout: Timeout) { done in auth.login(phoneNumber: Phone, code: OTP, scope: "email phone").start { result in expect(result).to(beSuccessful()) done() @@ -1057,10 +1049,8 @@ class AuthenticationSpec: QuickSpec { } it("should include audience if it is not nil") { - stub(condition: isToken(Domain) && hasAtLeast(["audience": "https://myapi.com/api"])) { _ in - return authResponse(accessToken: AccessToken) - } - await waitUntil(timeout: Timeout) { done in + NetworkStub.addStub(condition: { $0.isToken(Domain) && $0.hasAtLeast(["audience": "https://myapi.com/api"])}, response: authResponse(accessToken: AccessToken)) + waitUntil(timeout: Timeout) { done in auth.login(phoneNumber: Phone, code: OTP, audience: "https://myapi.com/api").start { result in expect(result).to(beSuccessful()) done() @@ -1069,10 +1059,8 @@ class AuthenticationSpec: QuickSpec { } it("should not include audience if it is nil") { - stub(condition: isToken(Domain) && hasNoneOf(["audience"])) { _ in - return authResponse(accessToken: AccessToken) - } - await waitUntil(timeout: Timeout) { done in + NetworkStub.addStub(condition: { $0.isToken(Domain) && $0.hasNoneOf(["audience"])}, response: authResponse(accessToken: AccessToken)) + waitUntil(timeout: Timeout) { done in auth.login(phoneNumber: Phone, code: OTP, audience: nil).start { result in expect(result).to(beSuccessful()) done() @@ -1081,35 +1069,33 @@ class AuthenticationSpec: QuickSpec { } it("should not include audience by default") { - stub(condition: isToken(Domain) && hasNoneOf(["audience"])) { _ in - return authResponse(accessToken: AccessToken) - } - await waitUntil(timeout: Timeout) { done in + NetworkStub.addStub(condition: { $0.isToken(Domain) && $0.hasNoneOf(["audience"])}, response: authResponse(accessToken: AccessToken)) + waitUntil(timeout: Timeout) { done in auth.login(phoneNumber: Phone, code: OTP).start { result in expect(result).to(beSuccessful()) done() } } } - + } } - + describe("user information") { - + it("should return user information") { - stub(condition: isUserInfo(Domain) && hasBearerToken(AccessToken)) { _ in return apiSuccessResponse(json: basicProfile()) }.name = "user info" - await waitUntil(timeout: Timeout) { done in + NetworkStub.addStub(condition: { $0.isUserInfo(Domain) && $0.hasBearerToken(AccessToken)}, response: apiSuccessResponse(json: basicProfile())) + waitUntil(timeout: Timeout) { done in auth.userInfo(withAccessToken: AccessToken).start { result in expect(result).to(haveProfile(Sub)) done() } } } - + it("should fail to get user info") { - stub(condition: isUserInfo(Domain)) { _ in return authFailure(error: "invalid_token", description: "the token is invalid") }.name = "token info failed" - await waitUntil(timeout: Timeout) { done in + NetworkStub.addStub(condition: { $0.isUserInfo(Domain) }, response: authFailure(error: "invalid_token", description: "the token is invalid")) + waitUntil(timeout: Timeout) { done in auth.userInfo(withAccessToken: AccessToken).start { result in expect(result).to(haveAuthenticationError(code: "invalid_token", description: "the token is invalid")) done() @@ -1118,60 +1104,58 @@ class AuthenticationSpec: QuickSpec { } } - + describe("code exchange") { - + var code: String! var codeVerifier: String! let redirectURI = "https://samples.auth0.com/callback" - + beforeEach { code = UUID().uuidString.replacingOccurrences(of: "-", with: "") codeVerifier = UUID().uuidString.replacingOccurrences(of: "-", with: "") } - - + + it("should exchange code for tokens") { - stub(condition: isToken(Domain) && hasAtLeast(["code": code, "code_verifier": codeVerifier, "grant_type": "authorization_code", "redirect_uri": redirectURI])) { _ in return authResponse(accessToken: AccessToken, idToken: IdToken) }.name = "Code Exchange Auth" - await waitUntil(timeout: Timeout) { done in + NetworkStub.addStub(condition: { $0.isToken(Domain) && $0.hasAtLeast(["code": code, "code_verifier": codeVerifier, "grant_type": "authorization_code", "redirect_uri": redirectURI])}, response:authResponse(accessToken: AccessToken, idToken: IdToken)) + waitUntil(timeout: Timeout) { done in auth.codeExchange(withCode: code, codeVerifier: codeVerifier, redirectURI: redirectURI).start { result in expect(result).to(haveCredentials(AccessToken, IdToken)) done() } } } - + it("should provide error payload from auth api") { - await waitUntil(timeout: Timeout) { done in + waitUntil(timeout: Timeout) { done in let code = "invalid_code" let description = "Invalid code" let invalidCode = "return invalid code" - stub(condition: isToken(Domain) && hasAtLeast(["code": invalidCode])) { _ in return authFailure(code: code, description: description) }.name = "Invalid Code" + NetworkStub.addStub(condition: { $0.isToken(Domain) && $0.hasAtLeast(["code": invalidCode])}, response:authFailure(code: code, description: description)) auth.codeExchange(withCode: invalidCode, codeVerifier: codeVerifier, redirectURI: redirectURI).start { result in expect(result).to(haveAuthenticationError(code: code, description: description)) done() } } } - + } - + describe("jwks") { it("should fetch the jwks") { - stub(condition: isJWKSPath(Domain)) { _ in jwksResponse() } - - await waitUntil { done in + NetworkStub.addStub(condition: { $0.isJWKSPath(Domain) }, response: jwksResponse()) + waitUntil { done in auth.jwks().start { expect($0).to(haveJWKS()) done() } } } - + it("should produce an error") { - stub(condition: isJWKSPath(Domain)) { _ in apiFailureResponse() } - - await waitUntil { done in + NetworkStub.addStub(condition: { $0.isJWKSPath(Domain) }, response: apiFailureResponse()) + waitUntil { done in auth.jwks().start { expect($0).to(beUnsuccessful()) done() @@ -1179,6 +1163,6 @@ class AuthenticationSpec: QuickSpec { } } } - + } } diff --git a/Auth0Tests/BioAuthenticationSpec.swift b/Auth0Tests/BioAuthenticationSpec.swift index c10b3a61..56c13498 100644 --- a/Auth0Tests/BioAuthenticationSpec.swift +++ b/Auth0Tests/BioAuthenticationSpec.swift @@ -6,7 +6,7 @@ import LocalAuthentication class BioAuthenticationSpec: QuickSpec { - override func spec() { + override class func spec() { var evaluationPolicy: LAPolicy! var mockContext: MockLAContext! var bioAuthentication: BioAuthentication! @@ -72,21 +72,21 @@ class BioAuthenticationSpec: QuickSpec { it("should authenticate") { bioAuthentication.validateBiometric { error = $0 } - await expect(error).toEventually(beNil()) + expect(error).toEventually(beNil()) } it("should return error on touch authentication") { let touchError = LAError(.appCancel) mockContext.replyError = touchError bioAuthentication.validateBiometric { error = $0 } - await expect(error).toEventually(matchError(touchError)) + expect(error).toEventually(matchError(touchError)) } it("should return authenticationFailed error if no policy success") { let touchError = LAError(.authenticationFailed) mockContext.replySuccess = false bioAuthentication.validateBiometric { error = $0 } - await expect(error).toEventually(matchError(touchError)) + expect(error).toEventually(matchError(touchError)) } it("should evaluate passed policy") { diff --git a/Auth0Tests/ChallengeGeneratorSpec.swift b/Auth0Tests/ChallengeGeneratorSpec.swift index 937dc050..629f241b 100644 --- a/Auth0Tests/ChallengeGeneratorSpec.swift +++ b/Auth0Tests/ChallengeGeneratorSpec.swift @@ -6,7 +6,7 @@ import Nimble class ChallengeGeneratorSpec: QuickSpec { - override func spec() { + override class func spec() { describe("test vector") { let seed: [UInt8] = [116, 24, 223, 180, 151, 153, 224, 37, 79, 250, 96, 125, 216, 173, diff --git a/Auth0Tests/ClaimValidatorsSpec.swift b/Auth0Tests/ClaimValidatorsSpec.swift index 0a2bdb0f..a138d600 100644 --- a/Auth0Tests/ClaimValidatorsSpec.swift +++ b/Auth0Tests/ClaimValidatorsSpec.swift @@ -6,7 +6,7 @@ import Nimble class ClaimValidatorsSpec: IDTokenValidatorBaseSpec { - override func spec() { + override class func spec() { describe("claims validation") { diff --git a/Auth0Tests/ClearSessionTransactionSpec.swift b/Auth0Tests/ClearSessionTransactionSpec.swift index 3b4f495b..0ee09cf3 100644 --- a/Auth0Tests/ClearSessionTransactionSpec.swift +++ b/Auth0Tests/ClearSessionTransactionSpec.swift @@ -6,7 +6,7 @@ import Nimble class ClearSessionTransactionSpec: QuickSpec { - override func spec() { + override class func spec() { var transaction: ClearSessionTransaction! beforeEach { diff --git a/Auth0Tests/CredentialsManagerErrorSpec.swift b/Auth0Tests/CredentialsManagerErrorSpec.swift index 2b08705b..eb0aa70b 100644 --- a/Auth0Tests/CredentialsManagerErrorSpec.swift +++ b/Auth0Tests/CredentialsManagerErrorSpec.swift @@ -6,7 +6,7 @@ import Nimble class CredentialsManagerErrorSpec: QuickSpec { - override func spec() { + override class func spec() { describe("init") { diff --git a/Auth0Tests/CredentialsManagerSpec.swift b/Auth0Tests/CredentialsManagerSpec.swift index 65d312e3..9a847ed6 100644 --- a/Auth0Tests/CredentialsManagerSpec.swift +++ b/Auth0Tests/CredentialsManagerSpec.swift @@ -2,10 +2,6 @@ import Combine import Quick import Nimble import SimpleKeychain -import OHHTTPStubs -#if SWIFT_PACKAGE -import OHHTTPStubsSwift -#endif #if WEB_AUTH_PLATFORM import LocalAuthentication #endif @@ -30,7 +26,7 @@ private let ValidToken = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ0ZXN0I class CredentialsManagerSpec: QuickSpec { - override func spec() { + override class func spec() { let authentication = Auth0.authentication(clientId: ClientId, domain: Domain) var credentialsManager: CredentialsManager! @@ -39,11 +35,12 @@ class CredentialsManagerSpec: QuickSpec { beforeEach { credentialsManager = CredentialsManager(authentication: authentication) credentials = Credentials(accessToken: AccessToken, tokenType: TokenType, idToken: IdToken, refreshToken: RefreshToken, expiresIn: Date(timeIntervalSinceNow: ExpiresIn)) - stub(condition: isHost(Domain)) { _ in catchAllResponse() }.name = "YOU SHALL NOT PASS!" + URLProtocol.registerClass(StubURLProtocol.self) } afterEach { - HTTPStubs.removeAllStubs() + NetworkStub.clearStubs() + URLProtocol.unregisterClass(StubURLProtocol.self) } describe("storage") { @@ -124,8 +121,7 @@ class CredentialsManagerSpec: QuickSpec { beforeEach { _ = credentialsManager.store(credentials: credentials) - - stub(condition: isRevokeToken(Domain) && hasAtLeast(["token": RefreshToken])) { _ in return revokeTokenResponse() }.name = "revoke success" + NetworkStub.addStub(condition: { $0.isRevokeToken(Domain) && $0.hasAtLeast(["token": RefreshToken])}, response: revokeTokenResponse()) } afterEach { @@ -133,7 +129,7 @@ class CredentialsManagerSpec: QuickSpec { } it("should clear credentials and revoke the refresh token") { - await waitUntil(timeout: Timeout) { done in + waitUntil(timeout: Timeout) { done in credentialsManager.revoke { result in expect(result).to(beSuccessful()) expect(credentialsManager.hasValid()).to(beFalse()) @@ -145,7 +141,7 @@ class CredentialsManagerSpec: QuickSpec { it("should not return an error if there were no credentials stored") { _ = credentialsManager.clear() - await waitUntil(timeout: Timeout) { done in + waitUntil(timeout: Timeout) { done in credentialsManager.revoke { result in expect(result).to(beSuccessful()) expect(credentialsManager.hasValid()).to(beFalse()) @@ -161,7 +157,7 @@ class CredentialsManagerSpec: QuickSpec { _ = credentialsManager.store(credentials: credentials) - await waitUntil(timeout: Timeout) { done in + waitUntil(timeout: Timeout) { done in credentialsManager.revoke { result in expect(result).to(beSuccessful()) expect(credentialsManager.hasValid()).to(beFalse()) @@ -173,10 +169,9 @@ class CredentialsManagerSpec: QuickSpec { it("should return the failure if the token could not be revoked, and not clear credentials") { let cause = AuthenticationError(description: "Revoke failed", statusCode: 400) let expectedError = CredentialsManagerError(code: .revokeFailed, cause: cause) - stub(condition: isRevokeToken(Domain) && hasAtLeast(["token": RefreshToken])) { _ in - return authFailure(code: "400", description: "Revoke failed") - } - await waitUntil(timeout: Timeout) { done in + NetworkStub.clearStubs() + NetworkStub.addStub(condition: { $0.isRevokeToken(Domain) && $0.hasAtLeast(["token": RefreshToken])}, response: authFailure(code: "400", description: "Revoke failed")) + waitUntil(timeout: Timeout) { done in credentialsManager.revoke { result in expect(result).to(haveCredentialsManagerError(expectedError)) expect(credentialsManager.hasValid()).to(beTrue()) @@ -188,8 +183,8 @@ class CredentialsManagerSpec: QuickSpec { it("should include custom headers") { let key = "foo" let value = "bar" - stub(condition: hasHeader(key, value: value)) { _ in return revokeTokenResponse() }.name = "revoke success" - await waitUntil(timeout: Timeout) { done in + NetworkStub.addStub(condition: { $0.hasHeader(key, value: value)}, response: revokeTokenResponse()) + waitUntil(timeout: Timeout) { done in credentialsManager.revoke(headers: [key: value], { result in expect(result).to(beSuccessful()) done() @@ -212,14 +207,14 @@ class CredentialsManagerSpec: QuickSpec { expect(credentialsManager.store(credentials: credentials)).to(beTrue()) expect(secondaryCredentialsManager.store(credentials: secondaryCredentials)).to(beTrue()) - await waitUntil(timeout: .seconds(200)) { done in + waitUntil(timeout: .seconds(200)) { done in credentialsManager.credentials { result in expect(result).to(haveCredentials(AccessToken, IdToken, RefreshToken)) done() } } - await waitUntil(timeout: Timeout) { done in + waitUntil(timeout: Timeout) { done in secondaryCredentialsManager.credentials { result in expect(result).to(haveCredentials("SecondaryAccessToken", "SecondaryIdToken", "SecondaryRefreshToken")) done() @@ -239,14 +234,14 @@ class CredentialsManagerSpec: QuickSpec { expect(credentialsManager.store(credentials: credentials)).to(beTrue()) - await waitUntil(timeout: Timeout) { done in + waitUntil(timeout: Timeout) { done in credentialsManager.credentials { result in expect(result).to(haveCredentials(AccessToken, IdToken, RefreshToken)) done() } } - await waitUntil(timeout: Timeout) { done in + waitUntil(timeout: Timeout) { done in secondaryCredentialsManager.credentials { result in expect(result).to(haveCredentials(AccessToken, IdToken, RefreshToken)) done() @@ -406,9 +401,7 @@ class CredentialsManagerSpec: QuickSpec { describe("retrieval") { beforeEach { - stub(condition: isToken(Domain) && hasAtLeast(["refresh_token": RefreshToken])) { _ in - return authResponse(accessToken: NewAccessToken, idToken: NewIdToken, refreshToken: nil, expiresIn: ExpiresIn * 2) - }.name = "renewal succeeded" + NetworkStub.addStub(condition: { $0.isToken(Domain) && $0.hasAtLeast(["refresh_token": RefreshToken])}, response: authResponse(accessToken: NewAccessToken, idToken: NewIdToken, refreshToken: nil, expiresIn: ExpiresIn * 2)) } afterEach { @@ -418,7 +411,7 @@ class CredentialsManagerSpec: QuickSpec { it("should error when no credentials stored") { _ = credentialsManager.clear() - await waitUntil(timeout: Timeout) { done in + waitUntil(timeout: Timeout) { done in credentialsManager.credentials { result in expect(result).to(haveCredentialsManagerError(CredentialsManagerError(code: .noCredentials))) done() @@ -430,7 +423,7 @@ class CredentialsManagerSpec: QuickSpec { credentials = Credentials(accessToken: AccessToken, tokenType: TokenType, idToken: IdToken, refreshToken: nil, expiresIn: Date(timeIntervalSinceNow: -ExpiresIn)) _ = credentialsManager.store(credentials: credentials) - await waitUntil(timeout: Timeout) { done in + waitUntil(timeout: Timeout) { done in credentialsManager.credentials { result in expect(result).to(haveCredentialsManagerError(CredentialsManagerError(code: .noRefreshToken))) done() @@ -441,7 +434,7 @@ class CredentialsManagerSpec: QuickSpec { it("should return original credentials as not expired") { _ = credentialsManager.store(credentials: credentials) - await waitUntil(timeout: Timeout) { done in + waitUntil(timeout: Timeout) { done in credentialsManager.credentials { result in expect(result).to(haveCredentials()) done() @@ -449,7 +442,7 @@ class CredentialsManagerSpec: QuickSpec { } } - #if os(iOS) + #if os(iOS) || os(visionOS) context("require biometrics") { it("should error when biometrics are unavailable") { @@ -458,7 +451,7 @@ class CredentialsManagerSpec: QuickSpec { credentials = Credentials(accessToken: AccessToken, tokenType: TokenType, idToken: IdToken, refreshToken: RefreshToken, expiresIn: Date(timeIntervalSinceNow: -ExpiresIn)) _ = credentialsManager.store(credentials: credentials) - await waitUntil(timeout: Timeout) { done in + waitUntil(timeout: Timeout) { done in credentialsManager.credentials { result in expect(result).to(haveCredentialsManagerError(expectedError)) done() @@ -476,7 +469,7 @@ class CredentialsManagerSpec: QuickSpec { credentials = Credentials(accessToken: AccessToken, tokenType: TokenType, idToken: IdToken, refreshToken: RefreshToken, expiresIn: Date(timeIntervalSinceNow: -ExpiresIn)) _ = credentialsManager.store(credentials: credentials) - await waitUntil(timeout: Timeout) { done in + waitUntil(timeout: Timeout) { done in credentialsManager.credentials { result in expect(result).to(haveCredentialsManagerError(expectedError)) done() @@ -491,7 +484,7 @@ class CredentialsManagerSpec: QuickSpec { credentials = Credentials(accessToken: AccessToken, tokenType: TokenType, idToken: IdToken, refreshToken: RefreshToken, expiresIn: Date(timeIntervalSinceNow: -ExpiresIn)) _ = credentialsManager.store(credentials: credentials) - await waitUntil(timeout: Timeout) { done in + waitUntil(timeout: Timeout) { done in credentialsManager.credentials { result in expect(result).to(haveCredentials(NewAccessToken, NewIdToken, RefreshToken)) done() @@ -507,7 +500,7 @@ class CredentialsManagerSpec: QuickSpec { it("should yield new credentials without refresh token rotation") { credentials = Credentials(accessToken: AccessToken, tokenType: TokenType, idToken: IdToken, refreshToken: RefreshToken, expiresIn: Date(timeIntervalSinceNow: -ExpiresIn)) _ = credentialsManager.store(credentials: credentials) - await waitUntil(timeout: Timeout) { done in + waitUntil(timeout: Timeout) { done in credentialsManager.credentials { result in expect(result).to(haveCredentials(NewAccessToken, NewIdToken, RefreshToken)) done() @@ -516,12 +509,11 @@ class CredentialsManagerSpec: QuickSpec { } it("should yield new credentials with refresh token rotation") { - stub(condition: isToken(Domain) && hasAtLeast(["refresh_token": RefreshToken])) { - _ in return authResponse(accessToken: NewAccessToken, idToken: NewIdToken, refreshToken: NewRefreshToken, expiresIn: ExpiresIn) - } + NetworkStub.clearStubs() + NetworkStub.addStub(condition: { $0.isToken(Domain) && $0.hasAtLeast(["refresh_token": RefreshToken])}, response: authResponse(accessToken: NewAccessToken, idToken: NewIdToken, refreshToken: NewRefreshToken, expiresIn: ExpiresIn)) credentials = Credentials(accessToken: AccessToken, tokenType: TokenType, idToken: IdToken, refreshToken: RefreshToken, expiresIn: Date(timeIntervalSinceNow: -ExpiresIn)) _ = credentialsManager.store(credentials: credentials) - await waitUntil(timeout: Timeout) { done in + waitUntil(timeout: Timeout) { done in credentialsManager.credentials { result in expect(result).to(haveCredentials(NewAccessToken, NewIdToken, NewRefreshToken)) done() @@ -534,7 +526,7 @@ class CredentialsManagerSpec: QuickSpec { credentialsManager = CredentialsManager(authentication: authentication, storage: store) credentials = Credentials(accessToken: AccessToken, tokenType: TokenType, idToken: IdToken, refreshToken: RefreshToken, expiresIn: Date(timeIntervalSinceNow: -ExpiresIn)) _ = credentialsManager.store(credentials: credentials) - await waitUntil(timeout: Timeout) { done in + waitUntil(timeout: Timeout) { done in credentialsManager.credentials { result in expect(result).to(beSuccessful()) let storedCredentials = try? NSKeyedUnarchiver.unarchivedObject(ofClass: Credentials.self, from: store.data(forKey: "credentials")) @@ -547,14 +539,13 @@ class CredentialsManagerSpec: QuickSpec { } it("should yield error on failed renewal") { + NetworkStub.clearStubs() let cause = AuthenticationError(info: ["error": "invalid_request", "error_description": "missing_params"]) let expectedError = CredentialsManagerError(code: .renewFailed, cause: cause) - stub(condition: isToken(Domain) && hasAtLeast(["refresh_token": RefreshToken])) { _ in - return authFailure(code: "invalid_request", description: "missing_params") - }.name = "renewal failed" + NetworkStub.addStub(condition: { $0.isToken(Domain) && $0.hasAtLeast(["refresh_token": RefreshToken])}, response: authFailure(code: "invalid_request", description: "missing_params")) credentials = Credentials(accessToken: AccessToken, tokenType: TokenType, idToken: IdToken, refreshToken: RefreshToken, expiresIn: Date(timeIntervalSinceNow: -ExpiresIn)) _ = credentialsManager.store(credentials: credentials) - await waitUntil(timeout: Timeout) { done in + waitUntil(timeout: Timeout) { done in credentialsManager.credentials { result in expect(result).to(haveCredentialsManagerError(expectedError)) done() @@ -583,7 +574,7 @@ class CredentialsManagerSpec: QuickSpec { } credentialsManager = CredentialsManager(authentication: authentication, storage: MockStore()) - await waitUntil(timeout: Timeout) { done in + waitUntil(timeout: Timeout) { done in credentialsManager.credentials { result in expect(result).to(haveCredentialsManagerError(.storeFailed)) done() @@ -593,12 +584,11 @@ class CredentialsManagerSpec: QuickSpec { it("renewal request should include custom parameters") { let someId = UUID().uuidString - stub(condition: isToken(Domain) && hasAtLeast(["refresh_token": RefreshToken, "some_id": someId])) { - _ in return authResponse(accessToken: NewAccessToken, idToken: NewIdToken, refreshToken: NewRefreshToken, expiresIn: ExpiresIn) - } + NetworkStub.clearStubs() + NetworkStub.addStub(condition: { $0.isToken(Domain) && $0.hasAtLeast(["refresh_token": RefreshToken, "some_id": someId])}, response: authResponse(accessToken: NewAccessToken, idToken: NewIdToken, refreshToken: NewRefreshToken, expiresIn: ExpiresIn)) credentials = Credentials(accessToken: AccessToken, tokenType: TokenType, idToken: IdToken, refreshToken: RefreshToken, expiresIn: Date(timeIntervalSinceNow: -ExpiresIn)) _ = credentialsManager.store(credentials: credentials) - await waitUntil(timeout: Timeout) { done in + waitUntil(timeout: Timeout) { done in credentialsManager.credentials(parameters: ["some_id": someId]) { result in expect(result).to(haveCredentials(NewAccessToken, NewIdToken, NewRefreshToken)) done() @@ -609,12 +599,10 @@ class CredentialsManagerSpec: QuickSpec { it("renewal request should include custom headers") { let key = "foo" let value = "bar" - stub(condition: hasHeader(key, value: value)) { - _ in return authResponse(accessToken: NewAccessToken, idToken: NewIdToken, refreshToken: NewRefreshToken, expiresIn: ExpiresIn) - } + NetworkStub.addStub(condition: { $0.hasHeader(key, value: value)}, response: authResponse(accessToken: NewAccessToken, idToken: NewIdToken, refreshToken: NewRefreshToken, expiresIn: ExpiresIn)) credentials = Credentials(accessToken: AccessToken, tokenType: TokenType, idToken: IdToken, refreshToken: RefreshToken, expiresIn: Date(timeIntervalSinceNow: -ExpiresIn)) _ = credentialsManager.store(credentials: credentials) - await waitUntil(timeout: Timeout) { done in + waitUntil(timeout: Timeout) { done in credentialsManager.credentials(headers: [key: value]) { result in expect(result).to(beSuccessful()) done() @@ -630,7 +618,7 @@ class CredentialsManagerSpec: QuickSpec { } it("should not yield a new access token by default") { - await waitUntil(timeout: Timeout) { done in + waitUntil(timeout: Timeout) { done in credentialsManager.credentials { result in expect(result).to(haveCredentials(AccessToken)) done() @@ -641,7 +629,7 @@ class CredentialsManagerSpec: QuickSpec { it("should not yield a new access token without a new scope") { credentials = Credentials(accessToken: AccessToken, refreshToken: RefreshToken, expiresIn: Date(timeIntervalSinceNow: ExpiresIn), scope: "openid profile") _ = credentialsManager.store(credentials: credentials) - await waitUntil(timeout: Timeout) { done in + waitUntil(timeout: Timeout) { done in credentialsManager.credentials(withScope: nil) { result in expect(result).to(haveCredentials(AccessToken)) done() @@ -652,7 +640,7 @@ class CredentialsManagerSpec: QuickSpec { it("should not yield a new access token with the same scope") { credentials = Credentials(accessToken: AccessToken, refreshToken: RefreshToken, expiresIn: Date(timeIntervalSinceNow: ExpiresIn), scope: "openid profile") _ = credentialsManager.store(credentials: credentials) - await waitUntil(timeout: Timeout) { done in + waitUntil(timeout: Timeout) { done in credentialsManager.credentials(withScope: "openid profile") { result in expect(result).to(haveCredentials(AccessToken)) done() @@ -663,7 +651,7 @@ class CredentialsManagerSpec: QuickSpec { it("should yield a new access token with a new scope") { credentials = Credentials(accessToken: AccessToken, refreshToken: RefreshToken, expiresIn: Date(timeIntervalSinceNow: ExpiresIn), scope: "openid profile") _ = credentialsManager.store(credentials: credentials) - await waitUntil(timeout: Timeout) { done in + waitUntil(timeout: Timeout) { done in credentialsManager.credentials(withScope: "openid profile offline_access") { result in expect(result).to(haveCredentials(NewAccessToken)) done() @@ -672,7 +660,7 @@ class CredentialsManagerSpec: QuickSpec { } it("should not yield a new access token with a min ttl less than its expiry") { - await waitUntil(timeout: Timeout) { done in + waitUntil(timeout: Timeout) { done in credentialsManager.credentials(minTTL: ValidTTL) { result in expect(result).to(haveCredentials(AccessToken)) done() @@ -681,7 +669,7 @@ class CredentialsManagerSpec: QuickSpec { } it("should yield a new access token with a min ttl greater than its expiry") { - await waitUntil(timeout: Timeout) { done in + waitUntil(timeout: Timeout) { done in credentialsManager.credentials(minTTL: InvalidTTL) { result in expect(result).to(haveCredentials(NewAccessToken)) done() @@ -690,13 +678,12 @@ class CredentialsManagerSpec: QuickSpec { } it("should fail to yield a renewed access token with a min ttl greater than its expiry") { + NetworkStub.clearStubs() let minTTL = 100_000 // The dates are not mocked, so they won't match exactly let expectedError = CredentialsManagerError(code: .largeMinTTL(minTTL: minTTL, lifetime: Int(ExpiresIn - 1))) - stub(condition: isToken(Domain) && hasAtLeast(["refresh_token": RefreshToken])) { - _ in return authResponse(accessToken: NewAccessToken, idToken: NewIdToken, refreshToken: NewRefreshToken, expiresIn: ExpiresIn) - } - await waitUntil(timeout: Timeout) { done in + NetworkStub.addStub(condition: { $0.isToken(Domain) && $0.hasAtLeast(["refresh_token": RefreshToken])}, response: authResponse(accessToken: NewAccessToken, idToken: NewIdToken, refreshToken: NewRefreshToken, expiresIn: ExpiresIn)) + waitUntil(timeout: Timeout) { done in credentialsManager.credentials(withScope: nil, minTTL: minTTL) { result in expect(result).to(haveCredentialsManagerError(expectedError)) done() @@ -709,12 +696,11 @@ class CredentialsManagerSpec: QuickSpec { context("serial renewal from same thread") { it("should yield the stored credentials after the previous renewal operation succeeded") { + NetworkStub.clearStubs() credentials = Credentials(accessToken: AccessToken, tokenType: TokenType, idToken: IdToken, refreshToken: RefreshToken, expiresIn: Date(timeIntervalSinceNow: -ExpiresIn)) _ = credentialsManager.store(credentials: credentials) - stub(condition: isToken(Domain) && hasAtLeast(["refresh_token": RefreshToken])) { _ in - return authResponse(accessToken: NewAccessToken, idToken: NewIdToken, refreshToken: NewRefreshToken, expiresIn: ExpiresIn) - } - await waitUntil(timeout: Timeout) { done in + NetworkStub.addStub(condition: { $0.isToken(Domain) && $0.hasAtLeast(["refresh_token": RefreshToken])}, response: authResponse(accessToken: NewAccessToken, idToken: NewIdToken, refreshToken: NewRefreshToken, expiresIn: ExpiresIn)) + waitUntil(timeout: Timeout) { done in credentialsManager.credentials { result in expect(result).to(haveCredentials(NewAccessToken, NewIdToken, NewRefreshToken)) } @@ -726,15 +712,15 @@ class CredentialsManagerSpec: QuickSpec { } it("should renew the credentials after the previous renewal operation failed") { + NetworkStub.clearStubs() credentials = Credentials(accessToken: AccessToken, tokenType: TokenType, idToken: IdToken, refreshToken: RefreshToken, expiresIn: Date(timeIntervalSinceNow: -ExpiresIn)) _ = credentialsManager.store(credentials: credentials) - stub(condition: isToken(Domain) && hasAtLeast(["refresh_token": RefreshToken])) { _ in return apiFailureResponse() } - await waitUntil(timeout: Timeout) { done in + NetworkStub.addStub(condition: { $0.isToken(Domain) && $0.hasAtLeast(["refresh_token": RefreshToken])}, response: apiFailureResponse()) + waitUntil(timeout: Timeout) { done in credentialsManager.credentials { result in expect(result).to(beUnsuccessful()) - stub(condition: isToken(Domain) && hasAtLeast(["refresh_token": RefreshToken])) { _ in - return authResponse(accessToken: NewAccessToken, idToken: NewIdToken, refreshToken: NewRefreshToken, expiresIn: ExpiresIn) - } + NetworkStub.clearStubs() + NetworkStub.addStub(condition: { $0.isToken(Domain) && $0.hasAtLeast(["refresh_token": RefreshToken])}, response: authResponse(accessToken: NewAccessToken, idToken: NewIdToken, refreshToken: NewRefreshToken, expiresIn: ExpiresIn)) } credentialsManager.credentials { result in expect(result).to(haveCredentials()) @@ -748,12 +734,11 @@ class CredentialsManagerSpec: QuickSpec { context("serial renewal from different threads") { it("should yield the stored credentials after the previous renewal operation succeeded") { + NetworkStub.clearStubs() credentials = Credentials(accessToken: AccessToken, tokenType: TokenType, idToken: IdToken, refreshToken: RefreshToken, expiresIn: Date(timeIntervalSinceNow: -ExpiresIn)) _ = credentialsManager.store(credentials: credentials) - stub(condition: isToken(Domain) && hasAtLeast(["refresh_token": RefreshToken, "request": "first"])) { request in - return authResponse(accessToken: NewAccessToken, idToken: NewIdToken, refreshToken: NewRefreshToken, expiresIn: ExpiresIn) - } - await waitUntil(timeout: Timeout) { [credentialsManager] done in + NetworkStub.addStub(condition: { $0.isToken(Domain) && $0.hasAtLeast(["refresh_token": RefreshToken, "request": "first"])}, response: authResponse(accessToken: NewAccessToken, idToken: NewIdToken, refreshToken: NewRefreshToken, expiresIn: ExpiresIn)) + waitUntil(timeout: Timeout) { [credentialsManager] done in DispatchQueue.global(qos: .utility).sync { credentialsManager?.credentials(parameters: ["request": "first"]) { result in expect(result).to(haveCredentials(NewAccessToken, NewIdToken, NewRefreshToken)) @@ -769,15 +754,13 @@ class CredentialsManagerSpec: QuickSpec { } it("should renew the credentials after the previous renewal operation failed") { + NetworkStub.clearStubs() credentials = Credentials(accessToken: AccessToken, tokenType: TokenType, idToken: IdToken, refreshToken: RefreshToken, expiresIn: Date(timeIntervalSinceNow: -ExpiresIn)) _ = credentialsManager.store(credentials: credentials) - stub(condition: isToken(Domain) && hasAtLeast(["refresh_token": RefreshToken, "request": "first"])) { request in - return apiFailureResponse() - } - stub(condition: isToken(Domain) && hasAtLeast(["refresh_token": RefreshToken, "request": "second"])) { request in - return authResponse(accessToken: NewAccessToken, idToken: NewIdToken, refreshToken: NewRefreshToken, expiresIn: ExpiresIn) - } - await waitUntil(timeout: Timeout) { [credentialsManager] done in + NetworkStub.addStub(condition: { $0.isToken(Domain) && $0.hasAtLeast(["refresh_token": RefreshToken, "request": "first"])}, response: apiFailureResponse()) + NetworkStub.addStub(condition: { $0.isToken(Domain) && $0.hasAtLeast(["refresh_token": RefreshToken, "request": "second"])}, response: authResponse(accessToken: NewAccessToken, idToken: NewIdToken, refreshToken: NewRefreshToken, expiresIn: ExpiresIn)) + + waitUntil(timeout: Timeout) { [credentialsManager] done in DispatchQueue.global(qos: .utility).sync { credentialsManager?.credentials(parameters: ["request": "first"]) { result in expect(result).to(beUnsuccessful()) @@ -798,9 +781,7 @@ class CredentialsManagerSpec: QuickSpec { describe("renew") { beforeEach { - stub(condition: isToken(Domain) && hasAtLeast(["refresh_token": RefreshToken])) { _ in - return authResponse(accessToken: NewAccessToken, idToken: NewIdToken, refreshToken: NewRefreshToken, expiresIn: ExpiresIn * 2) - }.name = "renewal succeeded" + NetworkStub.addStub(condition: { $0.isToken(Domain) && $0.hasAtLeast(["refresh_token": RefreshToken])}, response: authResponse(accessToken: NewAccessToken, idToken: NewIdToken, refreshToken: NewRefreshToken, expiresIn: ExpiresIn * 2)) } afterEach { @@ -810,7 +791,7 @@ class CredentialsManagerSpec: QuickSpec { it("should error when no credentials stored") { _ = credentialsManager.clear() - await waitUntil(timeout: Timeout) { done in + waitUntil(timeout: Timeout) { done in credentialsManager.renew { result in expect(result).to(haveCredentialsManagerError(CredentialsManagerError(code: .noCredentials))) done() @@ -822,7 +803,7 @@ class CredentialsManagerSpec: QuickSpec { credentials = Credentials(accessToken: AccessToken, tokenType: TokenType, idToken: IdToken, refreshToken: nil) _ = credentialsManager.store(credentials: credentials) - await waitUntil(timeout: Timeout) { done in + waitUntil(timeout: Timeout) { done in credentialsManager.renew { result in expect(result).to(haveCredentialsManagerError(CredentialsManagerError(code: .noRefreshToken))) done() @@ -831,11 +812,10 @@ class CredentialsManagerSpec: QuickSpec { } it("should yield new credentials without refresh token rotation") { - stub(condition: isToken(Domain) && hasAtLeast(["refresh_token": RefreshToken])) { _ in - return authResponse(accessToken: NewAccessToken, idToken: NewIdToken, refreshToken: nil, expiresIn: ExpiresIn * 2) - }.name = "renewal succeeded" + NetworkStub.clearStubs() + NetworkStub.addStub(condition: { $0.isToken(Domain) && $0.hasAtLeast(["refresh_token": RefreshToken])}, response: authResponse(accessToken: NewAccessToken, idToken: NewIdToken, refreshToken: nil, expiresIn: ExpiresIn * 2)) _ = credentialsManager.store(credentials: credentials) - await waitUntil(timeout: Timeout) { done in + waitUntil(timeout: Timeout) { done in credentialsManager.renew { result in expect(result).to(haveCredentials(NewAccessToken, NewIdToken, RefreshToken)) done() @@ -845,7 +825,7 @@ class CredentialsManagerSpec: QuickSpec { it("should yield new credentials with refresh token rotation") { _ = credentialsManager.store(credentials: credentials) - await waitUntil(timeout: Timeout) { done in + waitUntil(timeout: Timeout) { done in credentialsManager.renew { result in expect(result).to(haveCredentials(NewAccessToken, NewIdToken, NewRefreshToken)) done() @@ -857,7 +837,7 @@ class CredentialsManagerSpec: QuickSpec { let store = SimpleKeychain() credentialsManager = CredentialsManager(authentication: authentication, storage: store) _ = credentialsManager.store(credentials: credentials) - await waitUntil(timeout: Timeout) { done in + waitUntil(timeout: Timeout) { done in credentialsManager.renew { result in expect(result).to(beSuccessful()) let storedCredentials = try? NSKeyedUnarchiver.unarchivedObject(ofClass: Credentials.self, from: store.data(forKey: "credentials")) @@ -870,13 +850,12 @@ class CredentialsManagerSpec: QuickSpec { } it("should yield error on failed renewal") { + NetworkStub.clearStubs() let cause = AuthenticationError(info: ["error": "invalid_request", "error_description": "missing_params"]) let expectedError = CredentialsManagerError(code: .renewFailed, cause: cause) - stub(condition: isToken(Domain) && hasAtLeast(["refresh_token": RefreshToken])) { _ in - return authFailure(code: "invalid_request", description: "missing_params") - }.name = "renewal failed" + NetworkStub.addStub(condition: { $0.isToken(Domain) && $0.hasAtLeast(["refresh_token": RefreshToken])}, response: authFailure(code: "invalid_request", description: "missing_params")) _ = credentialsManager.store(credentials: credentials) - await waitUntil(timeout: Timeout) { done in + waitUntil(timeout: Timeout) { done in credentialsManager.renew { result in expect(result).to(haveCredentialsManagerError(expectedError)) done() @@ -901,7 +880,7 @@ class CredentialsManagerSpec: QuickSpec { } credentialsManager = CredentialsManager(authentication: authentication, storage: MockStore()) - await waitUntil(timeout: Timeout) { done in + waitUntil(timeout: Timeout) { done in credentialsManager.renew { result in expect(result).to(haveCredentialsManagerError(.storeFailed)) done() @@ -911,11 +890,9 @@ class CredentialsManagerSpec: QuickSpec { it("renewal request should include custom parameters") { let someId = UUID().uuidString - stub(condition: isToken(Domain) && hasAtLeast(["refresh_token": RefreshToken, "some_id": someId])) { - _ in return authResponse(accessToken: NewAccessToken, idToken: NewIdToken, refreshToken: NewRefreshToken, expiresIn: ExpiresIn) - } + NetworkStub.addStub(condition: { $0.isToken(Domain) && $0.hasAtLeast(["refresh_token": RefreshToken, "some_id": someId])}, response: authResponse(accessToken: NewAccessToken, idToken: NewIdToken, refreshToken: NewRefreshToken, expiresIn: ExpiresIn)) _ = credentialsManager.store(credentials: credentials) - await waitUntil(timeout: Timeout) { done in + waitUntil(timeout: Timeout) { done in credentialsManager.renew(parameters: ["some_id": someId]) { result in expect(result).to(haveCredentials(NewAccessToken, NewIdToken, NewRefreshToken)) done() @@ -926,11 +903,9 @@ class CredentialsManagerSpec: QuickSpec { it("renewal request should include custom headers") { let key = "foo" let value = "bar" - stub(condition: hasHeader(key, value: value)) { - _ in return authResponse(accessToken: NewAccessToken, idToken: NewIdToken, refreshToken: NewRefreshToken, expiresIn: ExpiresIn) - } + NetworkStub.addStub(condition: { $0.hasHeader(key, value: value)}, response: authResponse(accessToken: NewAccessToken, idToken: NewIdToken, refreshToken: NewRefreshToken, expiresIn: ExpiresIn)) _ = credentialsManager.store(credentials: credentials) - await waitUntil(timeout: Timeout) { done in + waitUntil(timeout: Timeout) { done in credentialsManager.renew(headers: [key: value]) { result in expect(result).to(beSuccessful()) done() @@ -945,18 +920,18 @@ class CredentialsManagerSpec: QuickSpec { let newAccessToken2 = "new-access-token-2" let newIDToken2 = "new-id-token-2" let newRefreshToken2 = "new-refresh-token-2" + + beforeEach { + NetworkStub.clearStubs() + } it("should renew the credentials serially from the same thread") { _ = credentialsManager.store(credentials: credentials) - stub(condition: isToken(Domain) && hasAtLeast(["refresh_token": RefreshToken, "request": "first"])) { request in - return authResponse(accessToken: newAccessToken1, idToken: newIDToken1, refreshToken: newRefreshToken1, expiresIn: ExpiresIn) - } - await waitUntil(timeout: Timeout) { done in + NetworkStub.addStub(condition: { $0.isToken(Domain) && $0.hasAtLeast(["refresh_token": RefreshToken, "request": "first"])}, response: authResponse(accessToken: newAccessToken1, idToken: newIDToken1, refreshToken: newRefreshToken1, expiresIn: ExpiresIn)) + waitUntil(timeout: Timeout) { done in credentialsManager.renew(parameters: ["request": "first"]) { result in expect(result).to(haveCredentials(newAccessToken1, newIDToken1, newRefreshToken1)) - stub(condition: isToken(Domain) && hasAtLeast(["refresh_token": newRefreshToken1, "request": "second"])) { request in - return authResponse(accessToken: newAccessToken2, idToken: newIDToken2, refreshToken: newRefreshToken2, expiresIn: ExpiresIn) - } + NetworkStub.addStub(condition: { $0.isToken(Domain) && $0.hasAtLeast(["refresh_token": newRefreshToken1, "request": "second"])}, response: authResponse(accessToken: newAccessToken2, idToken: newIDToken2, refreshToken: newRefreshToken2, expiresIn: ExpiresIn)) } credentialsManager.renew(parameters: ["request": "second"]) { result in expect(result).to(haveCredentials(newAccessToken2, newIDToken2, newRefreshToken2)) @@ -967,13 +942,9 @@ class CredentialsManagerSpec: QuickSpec { it("should renew the credentials serially from different threads") { _ = credentialsManager.store(credentials: credentials) - stub(condition: isToken(Domain) && hasAtLeast(["refresh_token": RefreshToken, "request": "first"])) { request in - return authResponse(accessToken: newAccessToken1, idToken: newIDToken1, refreshToken: newRefreshToken1, expiresIn: ExpiresIn) - } - stub(condition: isToken(Domain) && hasAtLeast(["refresh_token": newRefreshToken1, "request": "second"])) { request in - return authResponse(accessToken: newAccessToken2, idToken: newIDToken2, refreshToken: newRefreshToken2, expiresIn: ExpiresIn) - } - await waitUntil(timeout: Timeout) { [credentialsManager] done in + NetworkStub.addStub(condition: { $0.isToken(Domain) && $0.hasAtLeast(["refresh_token": RefreshToken, "request": "first"])}, response: authResponse(accessToken: newAccessToken1, idToken: newIDToken1, refreshToken: newRefreshToken1, expiresIn: ExpiresIn)) + NetworkStub.addStub(condition: { $0.isToken(Domain) && $0.hasAtLeast(["refresh_token": newRefreshToken1, "request": "second"])}, response: authResponse(accessToken: newAccessToken2, idToken: newIDToken2, refreshToken: newRefreshToken2, expiresIn: ExpiresIn)) + waitUntil(timeout: Timeout) { [credentialsManager] done in DispatchQueue.global(qos: .utility).sync { credentialsManager?.renew(parameters: ["request": "first"]) { result in expect(result).to(haveCredentials(newAccessToken1, newIDToken1, newRefreshToken1)) @@ -1004,7 +975,7 @@ class CredentialsManagerSpec: QuickSpec { it("should emit only one value") { _ = credentialsManager.store(credentials: credentials) - await waitUntil(timeout: Timeout) { done in + waitUntil(timeout: Timeout) { done in credentialsManager .credentials() .assertNoFailure() @@ -1019,7 +990,7 @@ class CredentialsManagerSpec: QuickSpec { it("should complete using the default parameter values") { _ = credentialsManager.store(credentials: credentials) - await waitUntil(timeout: Timeout) { done in + waitUntil(timeout: Timeout) { done in credentialsManager .credentials() .sink(receiveCompletion: { completion in @@ -1033,12 +1004,11 @@ class CredentialsManagerSpec: QuickSpec { it("should complete using custom parameter values") { let key = "foo" let value = "bar" - stub(condition: isToken(Domain) && hasAtLeast(["refresh_token": RefreshToken, key: value]) && hasHeader(key, value: value)) { _ in - return authResponse(accessToken: NewAccessToken, idToken: NewIdToken, refreshToken: NewRefreshToken, expiresIn: ExpiresIn) - } + + NetworkStub.addStub(condition: { $0.isToken(Domain) && $0.hasAtLeast(["refresh_token": RefreshToken, key: value]) && $0.hasHeader(key, value: value)}, response: authResponse(accessToken: NewAccessToken, idToken: NewIdToken, refreshToken: NewRefreshToken, expiresIn: ExpiresIn)) credentials = Credentials(accessToken: AccessToken, tokenType: TokenType, idToken: IdToken, refreshToken: RefreshToken, expiresIn: Date(timeIntervalSinceNow: -ExpiresIn)) _ = credentialsManager.store(credentials: credentials) - await waitUntil(timeout: Timeout) { done in + waitUntil(timeout: Timeout) { done in credentialsManager .credentials(withScope: "openid profile offline_access", minTTL: ValidTTL, @@ -1053,7 +1023,7 @@ class CredentialsManagerSpec: QuickSpec { } it("should complete with an error") { - await waitUntil(timeout: Timeout) { done in + waitUntil(timeout: Timeout) { done in credentialsManager .credentials() .ignoreOutput() @@ -1074,10 +1044,8 @@ class CredentialsManagerSpec: QuickSpec { } it("should emit only one value") { - stub(condition: isToken(Domain) && hasAtLeast(["refresh_token": RefreshToken])) { _ in - return authResponse(accessToken: NewAccessToken, idToken: NewIdToken, refreshToken: NewRefreshToken, expiresIn: ExpiresIn * 2) - }.name = "renewal succeeded" - await waitUntil(timeout: Timeout) { done in + NetworkStub.addStub(condition: { $0.isToken(Domain) && $0.hasAtLeast(["refresh_token": RefreshToken])}, response: authResponse(accessToken: NewAccessToken, idToken: NewIdToken, refreshToken: NewRefreshToken, expiresIn: ExpiresIn * 2)) + waitUntil(timeout: Timeout) { done in credentialsManager .renew() .assertNoFailure() @@ -1091,10 +1059,8 @@ class CredentialsManagerSpec: QuickSpec { } it("should complete using the default parameter values") { - stub(condition: isToken(Domain) && hasAtLeast(["refresh_token": RefreshToken])) { _ in - return authResponse(accessToken: NewAccessToken, idToken: NewIdToken, refreshToken: NewRefreshToken, expiresIn: ExpiresIn * 2) - }.name = "renewal succeeded" - await waitUntil(timeout: Timeout) { done in + NetworkStub.addStub(condition: { $0.isToken(Domain) && $0.hasAtLeast(["refresh_token": RefreshToken])}, response: authResponse(accessToken: NewAccessToken, idToken: NewIdToken, refreshToken: NewRefreshToken, expiresIn: ExpiresIn * 2)) + waitUntil(timeout: Timeout) { done in credentialsManager .renew() .sink(receiveCompletion: { completion in @@ -1108,10 +1074,8 @@ class CredentialsManagerSpec: QuickSpec { it("should complete using custom parameter values") { let key = "foo" let value = "bar" - stub(condition: isToken(Domain) && hasAtLeast(["refresh_token": RefreshToken, key: value]) && hasHeader(key, value: value)) { _ in - return authResponse(accessToken: NewAccessToken, idToken: NewIdToken, refreshToken: NewRefreshToken, expiresIn: ExpiresIn) - } - await waitUntil(timeout: Timeout) { done in + NetworkStub.addStub(condition: { $0.isToken(Domain) && $0.hasAtLeast(["refresh_token": RefreshToken, key: value])}, response: authResponse(accessToken: NewAccessToken, idToken: NewIdToken, refreshToken: NewRefreshToken, expiresIn: ExpiresIn)) + waitUntil(timeout: Timeout) { done in credentialsManager .renew(parameters: [key: value], headers: [key: value]) .sink(receiveCompletion: { completion in @@ -1123,10 +1087,8 @@ class CredentialsManagerSpec: QuickSpec { } it("should complete with an error") { - stub(condition: isToken(Domain) && hasAtLeast(["refresh_token": RefreshToken])) { _ in - return authFailure(code: "invalid_request", description: "missing_params") - }.name = "renewal failed" - await waitUntil(timeout: Timeout) { done in + NetworkStub.addStub(condition: { $0.isToken(Domain) && $0.hasAtLeast(["refresh_token": RefreshToken])}, response: authFailure(code: "invalid_request", description: "missing_params")) + waitUntil(timeout: Timeout) { done in credentialsManager .renew() .ignoreOutput() @@ -1143,11 +1105,9 @@ class CredentialsManagerSpec: QuickSpec { context("revoke") { it("should emit only one value") { - stub(condition: isRevokeToken(Domain) && hasAtLeast(["token": RefreshToken])) { _ in - return revokeTokenResponse() - } + NetworkStub.addStub(condition: { $0.isRevokeToken(Domain) && $0.hasAtLeast(["token": RefreshToken])}, response: revokeTokenResponse()) _ = credentialsManager.store(credentials: credentials) - await waitUntil(timeout: Timeout) { done in + waitUntil(timeout: Timeout) { done in credentialsManager .revoke() .assertNoFailure() @@ -1161,11 +1121,9 @@ class CredentialsManagerSpec: QuickSpec { } it("should complete using the default parameter values") { - stub(condition: isRevokeToken(Domain) && hasAtLeast(["token": RefreshToken])) { _ in - return revokeTokenResponse() - } + NetworkStub.addStub(condition: { $0.isRevokeToken(Domain) && $0.hasAtLeast(["token": RefreshToken])}, response: revokeTokenResponse()) _ = credentialsManager.store(credentials: credentials) - await waitUntil(timeout: Timeout) { done in + waitUntil(timeout: Timeout) { done in credentialsManager .revoke() .sink(receiveCompletion: { completion in @@ -1179,11 +1137,9 @@ class CredentialsManagerSpec: QuickSpec { it("should complete using custom parameter values") { let key = "foo" let value = "bar" - stub(condition: isRevokeToken(Domain) && hasAtLeast(["token": RefreshToken]) && hasHeader(key, value: value)) { _ in - return revokeTokenResponse() - } + NetworkStub.addStub(condition: { $0.isRevokeToken(Domain) && $0.hasAtLeast(["token": RefreshToken]) && $0.hasHeader(key, value: value)}, response: revokeTokenResponse()) _ = credentialsManager.store(credentials: credentials) - await waitUntil(timeout: Timeout) { done in + waitUntil(timeout: Timeout) { done in credentialsManager .revoke(headers: [key: value]) .sink(receiveCompletion: { completion in @@ -1195,11 +1151,9 @@ class CredentialsManagerSpec: QuickSpec { } it("should complete with an error") { - stub(condition: isRevokeToken(Domain) && hasAtLeast(["token": RefreshToken])) { _ in - return apiFailureResponse() - } + NetworkStub.addStub(condition: { $0.isRevokeToken(Domain) && $0.hasAtLeast(["token": RefreshToken])}, response: apiFailureResponse()) _ = credentialsManager.store(credentials: credentials) - await waitUntil(timeout: Timeout) { done in + waitUntil(timeout: Timeout) { done in credentialsManager .revoke() .ignoreOutput() @@ -1227,7 +1181,7 @@ class CredentialsManagerSpec: QuickSpec { it("should return the credentials using the default parameter values") { let credentialsManager = credentialsManager! _ = credentialsManager.store(credentials: credentials) - await waitUntil(timeout: Timeout) { done in + waitUntil(timeout: Timeout) { done in Task.init { _ = try await credentialsManager.credentials() done() @@ -1239,12 +1193,10 @@ class CredentialsManagerSpec: QuickSpec { let key = "foo" let value = "bar" let credentialsManager = credentialsManager! - stub(condition: isToken(Domain) && hasAtLeast(["refresh_token": RefreshToken, key: value]) && hasHeader(key, value: "bar")) { _ in - return authResponse(accessToken: NewAccessToken, idToken: NewIdToken, refreshToken: NewRefreshToken, expiresIn: ExpiresIn) - } + NetworkStub.addStub(condition: { $0.isToken(Domain) && $0.hasAtLeast(["refresh_token": RefreshToken, key: value]) && $0.hasHeader(key, value: value)}, response: authResponse(accessToken: NewAccessToken, idToken: NewIdToken, refreshToken: NewRefreshToken, expiresIn: ExpiresIn)) credentials = Credentials(accessToken: AccessToken, tokenType: TokenType, idToken: IdToken, refreshToken: RefreshToken, expiresIn: Date(timeIntervalSinceNow: -ExpiresIn)) _ = credentialsManager.store(credentials: credentials) - await waitUntil(timeout: Timeout) { done in + waitUntil(timeout: Timeout) { done in Task.init { _ = try await credentialsManager.credentials(withScope: "openid profile offline_access", minTTL: ValidTTL, @@ -1257,7 +1209,7 @@ class CredentialsManagerSpec: QuickSpec { it("should throw an error") { let credentialsManager = credentialsManager! - await waitUntil(timeout: Timeout) { done in + waitUntil(timeout: Timeout) { done in Task.init { do { _ = try await credentialsManager.credentials() @@ -1278,10 +1230,8 @@ class CredentialsManagerSpec: QuickSpec { it("should renew the credentials using the default parameter values") { let credentialsManager = credentialsManager! - stub(condition: isToken(Domain) && hasAtLeast(["refresh_token": RefreshToken])) { _ in - return authResponse(accessToken: NewAccessToken, idToken: NewIdToken, refreshToken: NewRefreshToken, expiresIn: ExpiresIn * 2) - }.name = "renewal succeeded" - await waitUntil(timeout: Timeout) { done in + NetworkStub.addStub(condition: { $0.isToken(Domain) && $0.hasAtLeast(["refresh_token": RefreshToken])}, response: authResponse(accessToken: NewAccessToken, idToken: NewIdToken, refreshToken: NewRefreshToken, expiresIn: ExpiresIn * 2)) + waitUntil(timeout: Timeout) { done in Task.init { let newCredentials = try await credentialsManager.renew() expect(newCredentials.accessToken) == NewAccessToken @@ -1296,10 +1246,8 @@ class CredentialsManagerSpec: QuickSpec { let key = "foo" let value = "bar" let credentialsManager = credentialsManager! - stub(condition: isToken(Domain) && hasAtLeast(["refresh_token": RefreshToken, key: value]) && hasHeader(key, value: "bar")) { _ in - return authResponse(accessToken: NewAccessToken, idToken: NewIdToken, refreshToken: NewRefreshToken, expiresIn: ExpiresIn) - } - await waitUntil(timeout: Timeout) { done in + NetworkStub.addStub(condition: { $0.isToken(Domain) && $0.hasAtLeast(["refresh_token": RefreshToken, key: value]) && $0.hasHeader(key, value: "bar")}, response: authResponse(accessToken: NewAccessToken, idToken: NewIdToken, refreshToken: NewRefreshToken, expiresIn: ExpiresIn)) + waitUntil(timeout: Timeout) { done in Task.init { let newCredentials = try await credentialsManager.renew(parameters: [key: value], headers: [key: value]) expect(newCredentials.accessToken) == NewAccessToken @@ -1312,10 +1260,8 @@ class CredentialsManagerSpec: QuickSpec { it("should throw an error") { let credentialsManager = credentialsManager! - stub(condition: isToken(Domain) && hasAtLeast(["refresh_token": RefreshToken])) { _ in - return authFailure(code: "invalid_request", description: "missing_params") - }.name = "renewal failed" - await waitUntil(timeout: Timeout) { done in + NetworkStub.addStub(condition: { $0.isToken(Domain) && $0.hasAtLeast(["refresh_token": RefreshToken])}, response: authFailure(code: "invalid_request", description: "missing_params")) + waitUntil(timeout: Timeout) { done in Task.init { do { _ = try await credentialsManager.renew() @@ -1332,11 +1278,9 @@ class CredentialsManagerSpec: QuickSpec { it("should revoke using the default parameter values") { let credentialsManager = credentialsManager! - stub(condition: isRevokeToken(Domain) && hasAtLeast(["token": RefreshToken])) { _ in - return revokeTokenResponse() - } + NetworkStub.addStub(condition: { $0.isRevokeToken(Domain) && $0.hasAtLeast(["token": RefreshToken])}, response: revokeTokenResponse()) _ = credentialsManager.store(credentials: credentials) - await waitUntil(timeout: Timeout) { done in + waitUntil(timeout: Timeout) { done in Task.init { _ = try await credentialsManager.revoke() done() @@ -1348,11 +1292,9 @@ class CredentialsManagerSpec: QuickSpec { let key = "foo" let value = "bar" let credentialsManager = credentialsManager! - stub(condition: isRevokeToken(Domain) && hasAtLeast(["token": RefreshToken]) && hasHeader(key, value: value)) { _ in - return revokeTokenResponse() - } + NetworkStub.addStub(condition: { $0.isRevokeToken(Domain) && $0.hasAtLeast(["token": RefreshToken]) && $0.hasHeader(key, value: value)}, response: revokeTokenResponse()) _ = credentialsManager.store(credentials: credentials) - await waitUntil(timeout: Timeout) { done in + waitUntil(timeout: Timeout) { done in Task.init { _ = try await credentialsManager.revoke(headers: [key: value]) done() @@ -1362,11 +1304,9 @@ class CredentialsManagerSpec: QuickSpec { it("should throw an error") { let credentialsManager = credentialsManager! - stub(condition: isRevokeToken(Domain) && hasAtLeast(["token": RefreshToken])) { _ in - return apiFailureResponse() - } + NetworkStub.addStub(condition: { $0.isRevokeToken(Domain) && $0.hasAtLeast(["token": RefreshToken])}, response: apiFailureResponse()) _ = credentialsManager.store(credentials: credentials) - await waitUntil(timeout: Timeout) { done in + waitUntil(timeout: Timeout) { done in Task.init { do { _ = try await credentialsManager.revoke() diff --git a/Auth0Tests/CredentialsSpec.swift b/Auth0Tests/CredentialsSpec.swift index a4cbcd46..9f6682d6 100644 --- a/Auth0Tests/CredentialsSpec.swift +++ b/Auth0Tests/CredentialsSpec.swift @@ -13,7 +13,7 @@ private let ExpiresInDate = Date(timeIntervalSinceNow: ExpiresIn) private let Scope = "openid" class CredentialsSpec: QuickSpec { - override func spec() { + override class func spec() { describe("decode from json") { diff --git a/Auth0Tests/IDTokenSignatureValidatorSpec.swift b/Auth0Tests/IDTokenSignatureValidatorSpec.swift index b69e37c2..1f9303b1 100644 --- a/Auth0Tests/IDTokenSignatureValidatorSpec.swift +++ b/Auth0Tests/IDTokenSignatureValidatorSpec.swift @@ -2,24 +2,21 @@ import Foundation import Quick import Nimble import JWTDecode -import OHHTTPStubs -#if SWIFT_PACKAGE -import OHHTTPStubsSwift -#endif @testable import Auth0 class IDTokenSignatureValidatorSpec: IDTokenValidatorBaseSpec { - override func spec() { + override class func spec() { let domain = self.domain beforeEach { - stub(condition: isHost(domain)) { _ in catchAllResponse() }.name = "YOU SHALL NOT PASS!" + URLProtocol.registerClass(StubURLProtocol.self) } afterEach { - HTTPStubs.removeAllStubs() + NetworkStub.clearStubs() + URLProtocol.unregisterClass(StubURLProtocol.self) } describe("signature validation") { @@ -27,11 +24,11 @@ class IDTokenSignatureValidatorSpec: IDTokenValidatorBaseSpec { context("algorithm support") { it("should support RS256") { - stub(condition: isJWKSPath(domain)) { _ in jwksResponse() } + NetworkStub.addStub(condition: { $0.isJWKSPath(domain) }, response: jwksResponse()) let jwt = generateJWT(alg: "RS256") - await waitUntil { done in + waitUntil { done in signatureValidator.validate(jwt) { error in expect(error).to(beNil()) done() @@ -44,7 +41,7 @@ class IDTokenSignatureValidatorSpec: IDTokenValidatorBaseSpec { let jwt = generateJWT(alg: alg) let expectedError = IDTokenSignatureValidator.ValidationError.invalidAlgorithm(actual: alg, expected: "RS256") - await waitUntil { done in + waitUntil { done in signatureValidator.validate(jwt) { error in expect(error).to(matchError(expectedError)) expect(error?.localizedDescription).to(equal(expectedError.localizedDescription)) @@ -58,7 +55,7 @@ class IDTokenSignatureValidatorSpec: IDTokenValidatorBaseSpec { let jwt = generateJWT(alg: alg) let expectedError = IDTokenSignatureValidator.ValidationError.invalidAlgorithm(actual: alg, expected: "RS256") - await waitUntil { done in + waitUntil { done in signatureValidator.validate(jwt) { error in expect(error).to(matchError(expectedError)) expect(error?.localizedDescription).to(equal(expectedError.localizedDescription)) @@ -68,12 +65,12 @@ class IDTokenSignatureValidatorSpec: IDTokenValidatorBaseSpec { } it("should fail with an incorrect signature") { - stub(condition: isJWKSPath(domain)) { _ in jwksResponse() } + NetworkStub.addStub(condition: { $0.isJWKSPath(domain) }, response: jwksResponse()) let jwt = generateJWT(alg: "RS256", signature: "foo") let expectedError = IDTokenSignatureValidator.ValidationError.invalidSignature - await waitUntil { done in + waitUntil { done in signatureValidator.validate(jwt) { error in expect(error).to(matchError(expectedError)) expect(error?.localizedDescription).to(equal(expectedError.localizedDescription)) @@ -88,9 +85,9 @@ class IDTokenSignatureValidatorSpec: IDTokenValidatorBaseSpec { let expectedError = IDTokenSignatureValidator.ValidationError.missingPublicKey(kid: Kid) it("should fail if the jwk has no kid") { - stub(condition: isJWKSPath(domain)) { _ in jwksResponse(kid: nil) } + NetworkStub.addStub(condition: { $0.isJWKSPath(domain) }, response: jwksResponse(kid: nil)) - await waitUntil { done in + waitUntil { done in signatureValidator.validate(jwt) { error in expect(error).to(matchError(expectedError)) expect(error?.localizedDescription).to(equal(expectedError.localizedDescription)) @@ -100,9 +97,9 @@ class IDTokenSignatureValidatorSpec: IDTokenValidatorBaseSpec { } it("should fail if the jwk kid does not match the jwt kid") { - stub(condition: isJWKSPath(domain)) { _ in jwksResponse(kid: "abc123") } + NetworkStub.addStub(condition: { $0.isJWKSPath(domain) }, response: jwksResponse(kid: "abc123")) - await waitUntil { done in + waitUntil { done in signatureValidator.validate(jwt) { error in expect(error).to(matchError(expectedError)) expect(error?.localizedDescription).to(equal(expectedError.localizedDescription)) @@ -112,9 +109,9 @@ class IDTokenSignatureValidatorSpec: IDTokenValidatorBaseSpec { } it("should fail if the keys cannot be retrieved") { - stub(condition: isJWKSPath(domain)) { _ in apiFailureResponse() } + NetworkStub.addStub(condition: { $0.isJWKSPath(domain) }, response: apiFailureResponse()) - await waitUntil { done in + waitUntil { done in signatureValidator.validate(jwt) { error in expect(error).to(matchError(expectedError)) expect(error?.localizedDescription).to(equal(expectedError.localizedDescription)) diff --git a/Auth0Tests/IDTokenValidatorBaseSpec.swift b/Auth0Tests/IDTokenValidatorBaseSpec.swift index 29f5b96b..d61b9982 100644 --- a/Auth0Tests/IDTokenValidatorBaseSpec.swift +++ b/Auth0Tests/IDTokenValidatorBaseSpec.swift @@ -4,16 +4,16 @@ import Quick @testable import Auth0 class IDTokenValidatorBaseSpec: QuickSpec { - let domain = "tokens-test.auth0.com" - let clientId = "e31f6f9827c187e8aebdb0839a0c963a" - let nonce = "a1b2c3d4e5" - let leeway = 60 * 1000 // 60 seconds - let maxAge = 1000 // 1 second - let organization = "abc1234" + static let domain = "tokens-test.auth0.com" + static let clientId = "e31f6f9827c187e8aebdb0839a0c963a" + static let nonce = "a1b2c3d4e5" + static let leeway = 60 * 1000 // 60 seconds + static let maxAge = 1000 // 1 second + static let organization = "abc1234" // Can't override the initWithInvocation: initializer, because NSInvocation is not available in Swift - lazy var authentication = Auth0.authentication(clientId: clientId, domain: domain) - lazy var validatorContext = IDTokenValidatorContext(issuer: URL.httpsURL(from: domain).absoluteString, + static let authentication = Auth0.authentication(clientId: clientId, domain: domain) + static let validatorContext = IDTokenValidatorContext(issuer: URL.httpsURL(from: domain).absoluteString, audience: clientId, jwksRequest: authentication.jwks(), leeway: leeway, diff --git a/Auth0Tests/IDTokenValidatorSpec.swift b/Auth0Tests/IDTokenValidatorSpec.swift index 151f4e08..33fb134e 100644 --- a/Auth0Tests/IDTokenValidatorSpec.swift +++ b/Auth0Tests/IDTokenValidatorSpec.swift @@ -1,27 +1,24 @@ import Foundation import Quick import Nimble -import OHHTTPStubs -#if SWIFT_PACKAGE -import OHHTTPStubsSwift -#endif @testable import Auth0 class IDTokenValidatorSpec: IDTokenValidatorBaseSpec { - override func spec() { + override class func spec() { let domain = self.domain let validatorContext = self.validatorContext let mockSignatureValidator = MockSuccessfulIDTokenSignatureValidator() let mockClaimsValidator = MockSuccessfulIDTokenClaimsValidator() beforeEach { - stub(condition: isHost(self.domain)) { _ in catchAllResponse() }.name = "YOU SHALL NOT PASS!" + URLProtocol.registerClass(StubURLProtocol.self) } afterEach { - HTTPStubs.removeAllStubs() + NetworkStub.clearStubs() + URLProtocol.unregisterClass(StubURLProtocol.self) } describe("top level validation api") { @@ -30,7 +27,7 @@ class IDTokenValidatorSpec: IDTokenValidatorBaseSpec { let expectedError = IDTokenDecodingError.cannotDecode it("should fail to decode an empty id token") { - await waitUntil { done in + waitUntil { done in validate(idToken: "", with: validatorContext, signatureValidator: mockSignatureValidator, @@ -43,7 +40,7 @@ class IDTokenValidatorSpec: IDTokenValidatorBaseSpec { } it("should fail to decode a malformed id token") { - await waitUntil { done in + waitUntil { done in validate(idToken: "a.b.c.d.e", with: validatorContext, signatureValidator: mockSignatureValidator, @@ -56,7 +53,7 @@ class IDTokenValidatorSpec: IDTokenValidatorBaseSpec { } it("should fail to decode an id token with an empty signature") { - await waitUntil { done in + waitUntil { done in validate(idToken: "a.b.", // alg == none, not supported by us with: validatorContext, signatureValidator: mockSignatureValidator, @@ -70,7 +67,7 @@ class IDTokenValidatorSpec: IDTokenValidatorBaseSpec { } it("should fail to decode an id token with no signature") { - await waitUntil { done in + waitUntil { done in validate(idToken: "a.b", with: validatorContext, signatureValidator: mockSignatureValidator, @@ -85,13 +82,13 @@ class IDTokenValidatorSpec: IDTokenValidatorBaseSpec { context("signature validation") { beforeEach { - stub(condition: isJWKSPath(domain)) { _ in jwksResponse() } + NetworkStub.addStub(condition: { $0.isJWKSPath(domain)}, response: jwksResponse()) } it("should validate a token signed with RS256") { let jwt = generateJWT(alg: "RS256") - await waitUntil { done in + waitUntil { done in validate(idToken: jwt.string, with: validatorContext, claimsValidator: mockClaimsValidator) { error in @@ -104,7 +101,7 @@ class IDTokenValidatorSpec: IDTokenValidatorBaseSpec { it("should not validate a token signed with an unsupported algorithm") { let jwt = generateJWT(alg: "HS256") - await waitUntil { done in + waitUntil { done in validate(idToken: jwt.string, with: validatorContext, claimsValidator: mockClaimsValidator) { error in @@ -117,7 +114,7 @@ class IDTokenValidatorSpec: IDTokenValidatorBaseSpec { it("should not validate an unsigned token") { let jwt = generateJWT(alg: "none") - await waitUntil { done in + waitUntil { done in validate(idToken: jwt.string, with: validatorContext, claimsValidator: mockClaimsValidator) { error in @@ -141,7 +138,7 @@ class IDTokenValidatorSpec: IDTokenValidatorBaseSpec { nonce: nil, organization: nil) - await waitUntil { done in + waitUntil { done in validate(idToken: jwt.string, with: context, signatureValidator: mockSignatureValidator) { error in @@ -163,7 +160,7 @@ class IDTokenValidatorSpec: IDTokenValidatorBaseSpec { nonce: nil, organization: nil) - await waitUntil { done in + waitUntil { done in validate(idToken: jwt.string, with: context, signatureValidator: mockSignatureValidator) { error in @@ -184,7 +181,7 @@ class IDTokenValidatorSpec: IDTokenValidatorBaseSpec { nonce: nonce, organization: nil) - await waitUntil { done in + waitUntil { done in validate(idToken: jwt.string, with: context, signatureValidator: mockSignatureValidator) { error in @@ -206,7 +203,7 @@ class IDTokenValidatorSpec: IDTokenValidatorBaseSpec { nonce: nil, organization: nil) - await waitUntil { done in + waitUntil { done in validate(idToken: jwt.string, with: context, signatureValidator: mockSignatureValidator) { error in @@ -227,7 +224,7 @@ class IDTokenValidatorSpec: IDTokenValidatorBaseSpec { nonce: nil, organization: orgID) - await waitUntil { done in + waitUntil { done in validate(idToken: jwt.string, with: context, signatureValidator: mockSignatureValidator) { error in @@ -248,7 +245,7 @@ class IDTokenValidatorSpec: IDTokenValidatorBaseSpec { nonce: nil, organization: orgName) - await waitUntil { done in + waitUntil { done in validate(idToken: jwt.string, with: context, signatureValidator: mockSignatureValidator) { error in @@ -270,7 +267,7 @@ class IDTokenValidatorSpec: IDTokenValidatorBaseSpec { organization: orgID) let expectedError = IDTokenOrgIDValidator.ValidationError.missingOrgId - await waitUntil { done in + waitUntil { done in validate(idToken: jwt.string, with: context, signatureValidator: mockSignatureValidator) { error in @@ -292,7 +289,7 @@ class IDTokenValidatorSpec: IDTokenValidatorBaseSpec { organization: orgName) let expectedError = IDTokenOrgNameValidator.ValidationError.missingOrgName - await waitUntil { done in + waitUntil { done in validate(idToken: jwt.string, with: context, signatureValidator: mockSignatureValidator) { error in @@ -315,7 +312,7 @@ class IDTokenValidatorSpec: IDTokenValidatorBaseSpec { let validator = IDTokenValidator(signatureValidator: spySignatureValidator, claimsValidator: mockClaimsValidator, context: validatorContext) - await waitUntil { done in + waitUntil { done in validator.validate(jwt) { _ in expect(spySignatureValidator.didExecuteInWorkerThread).to(beTrue()) done() @@ -328,7 +325,7 @@ class IDTokenValidatorSpec: IDTokenValidatorBaseSpec { let validator = IDTokenValidator(signatureValidator: mockSignatureValidator, claimsValidator: spyClaimsValidator, context: validatorContext) - await waitUntil { done in + waitUntil { done in validator.validate(jwt) { _ in expect(spyClaimsValidator.didExecuteInWorkerThread).to(beTrue()) done() @@ -341,7 +338,7 @@ class IDTokenValidatorSpec: IDTokenValidatorBaseSpec { claimsValidator: mockClaimsValidator, context: validatorContext) - await waitUntil { done in + waitUntil { done in validator.validate(jwt) { _ in expect(Thread.isMainThread).to(beTrue()) done() @@ -356,7 +353,7 @@ class IDTokenValidatorSpec: IDTokenValidatorBaseSpec { claimsValidator: mockClaimsValidator, context: validatorContext) - await waitUntil { done in + waitUntil { done in validator.validate(jwt) { error in expect(error).to(beNil()) done() @@ -370,7 +367,7 @@ class IDTokenValidatorSpec: IDTokenValidatorBaseSpec { claimsValidator: MockUnsuccessfulIDTokenClaimValidator(), context: validatorContext) - await waitUntil { done in + waitUntil { done in validator.validate(jwt) { error in expect(error).to(matchError(expectedError)) done() @@ -384,7 +381,7 @@ class IDTokenValidatorSpec: IDTokenValidatorBaseSpec { claimsValidator: mockClaimsValidator, context: validatorContext) - await waitUntil { done in + waitUntil { done in validator.validate(jwt) { error in expect(error).to(matchError(expectedError)) done() @@ -399,7 +396,7 @@ class IDTokenValidatorSpec: IDTokenValidatorBaseSpec { claimsValidator: spyClaimsValidator, context: validatorContext) - await waitUntil { done in + waitUntil { done in validator.validate(jwt) { error in expect(error).to(matchError(expectedError)) expect(spyClaimsValidator.didExecuteValidation).to(beFalse()) diff --git a/Auth0Tests/JWKSpec.swift b/Auth0Tests/JWKSpec.swift index 5cb1cb5a..ff7cc5df 100644 --- a/Auth0Tests/JWKSpec.swift +++ b/Auth0Tests/JWKSpec.swift @@ -6,7 +6,7 @@ import Nimble class JWKSpec: QuickSpec { - override func spec() { + override class func spec() { describe("public key generation") { diff --git a/Auth0Tests/JWTAlgorithmSpec.swift b/Auth0Tests/JWTAlgorithmSpec.swift index 0ee4fc05..b9276785 100644 --- a/Auth0Tests/JWTAlgorithmSpec.swift +++ b/Auth0Tests/JWTAlgorithmSpec.swift @@ -6,7 +6,7 @@ import Nimble class JWTAlgorithmSpec: QuickSpec { - override func spec() { + override class func spec() { let jwk = generateRSAJWK() describe("signature validation") { diff --git a/Auth0Tests/LoggerSpec.swift b/Auth0Tests/LoggerSpec.swift index eabe4973..bbb82b31 100644 --- a/Auth0Tests/LoggerSpec.swift +++ b/Auth0Tests/LoggerSpec.swift @@ -6,7 +6,7 @@ import Nimble class LoggerSpec: QuickSpec { - override func spec() { + override class func spec() { it("should build with default output") { let logger = DefaultLogger() diff --git a/Auth0Tests/LoginTransactionSpec.swift b/Auth0Tests/LoginTransactionSpec.swift index 6cad51d5..d7919cb9 100644 --- a/Auth0Tests/LoginTransactionSpec.swift +++ b/Auth0Tests/LoginTransactionSpec.swift @@ -6,7 +6,7 @@ import Nimble class LoginTransactionSpec: QuickSpec { - override func spec() { + override class func spec() { var transaction: LoginTransaction! let userAgent = SpyUserAgent() let handler = SpyGrant() @@ -21,6 +21,10 @@ class LoginTransactionSpec: QuickSpec { logger: DefaultLogger(output: loggerOutput), callback: { _ in }) } + + afterEach { + loggerOutput.messages.removeAll() + } describe("code exchange") { context("resume") { diff --git a/Auth0Tests/ManagementErrorSpec.swift b/Auth0Tests/ManagementErrorSpec.swift index fb3ac795..419b74dc 100644 --- a/Auth0Tests/ManagementErrorSpec.swift +++ b/Auth0Tests/ManagementErrorSpec.swift @@ -6,7 +6,7 @@ import Nimble class ManagementErrorSpec: QuickSpec { - override func spec() { + override class func spec() { describe("init") { diff --git a/Auth0Tests/ManagementSpec.swift b/Auth0Tests/ManagementSpec.swift index 7fb6aded..693783ec 100644 --- a/Auth0Tests/ManagementSpec.swift +++ b/Auth0Tests/ManagementSpec.swift @@ -10,7 +10,7 @@ private let DomainURL = URL(string: "https://\(Domain)")! class ManagementSpec: QuickSpec { - override func spec() { + override class func spec() { describe("init") { @@ -52,7 +52,7 @@ class ManagementSpec: QuickSpec { let response = Response(data: data, response: http, error: nil) var actual: ManagementResult? = nil management.managementObject(response: response) { actual = $0 } - await expect(actual).toEventually(haveObjectWithAttributes(["key"])) + expect(actual).toEventually(haveObjectWithAttributes(["key"])) } } @@ -66,7 +66,7 @@ class ManagementSpec: QuickSpec { let response = Response(data: data, response: http, error: nil) var actual: ManagementResult? = nil management.managementObject(response: response) { actual = $0 } - await expect(actual).toEventually(haveManagementError(description: errorBody, code: nonJSONError, statusCode: statusCode)) + expect(actual).toEventually(haveManagementError(description: errorBody, code: nonJSONError, statusCode: statusCode)) } it("should yield invalid response") { @@ -77,7 +77,7 @@ class ManagementSpec: QuickSpec { let response = Response(data: data, response: http, error: nil) var actual: ManagementResult? = nil management.managementObject(response: response) { actual = $0 } - await expect(actual).toEventually(haveManagementError(description: errorBody, code: nonJSONError, statusCode: statusCode)) + expect(actual).toEventually(haveManagementError(description: errorBody, code: nonJSONError, statusCode: statusCode)) } it("should yield generic failure when error response is unknown") { @@ -88,7 +88,7 @@ class ManagementSpec: QuickSpec { let response = Response(data: data, response: http, error: nil) var actual: ManagementResult? = nil management.managementObject(response: response) { actual = $0 } - await expect(actual).toEventually(haveManagementError(description: errorBody, code: nonJSONError, statusCode: statusCode)) + expect(actual).toEventually(haveManagementError(description: errorBody, code: nonJSONError, statusCode: statusCode)) } it("should yield server error") { @@ -99,7 +99,7 @@ class ManagementSpec: QuickSpec { let response = Response(data: data, response: http, error: nil) var actual: ManagementResult? = nil management.managementObject(response: response) { actual = $0 } - await expect(actual).toEventually(haveManagementError("error", description: "description", code: "code", statusCode: statusCode)) + expect(actual).toEventually(haveManagementError("error", description: "description", code: "code", statusCode: statusCode)) } it("should yield generic failure when no payload") { @@ -108,7 +108,7 @@ class ManagementSpec: QuickSpec { let response = Response(data: nil, response: http, error: nil) var actual: ManagementResult? = nil management.managementObject(response: response) { actual = $0 } - await expect(actual).toEventually(haveManagementError(description: "Empty response body.", code: emptyBodyError, statusCode: statusCode)) + expect(actual).toEventually(haveManagementError(description: "Empty response body.", code: emptyBodyError, statusCode: statusCode)) } it("should yield error with a cause") { @@ -117,7 +117,7 @@ class ManagementSpec: QuickSpec { let response = Response(data: nil, response: nil, error: cause) var actual: ManagementResult? = nil management.managementObject(response: response) { actual = $0 } - await expect(actual).toEventually(haveManagementError(description: description, code: nonJSONError, cause: cause)) + expect(actual).toEventually(haveManagementError(description: description, code: nonJSONError, cause: cause)) } } diff --git a/Auth0Tests/Matchers.swift b/Auth0Tests/Matchers.swift index 9c696f3a..f53cc506 100644 --- a/Auth0Tests/Matchers.swift +++ b/Auth0Tests/Matchers.swift @@ -1,137 +1,20 @@ import Foundation import Nimble -import OHHTTPStubs -#if SWIFT_PACKAGE -import OHHTTPStubsSwift -#endif @testable import Auth0 -func hasAllOf(_ parameters: [String: String]) -> HTTPStubsTestBlock { - return { request in - guard let payload = request.payload else { return false } - return parameters.count == payload.count && parameters.reduce(true, { (initial, entry) -> Bool in - return initial && payload[entry.0] as? String == entry.1 - }) - } -} - -func hasAtLeast(_ parameters: [String: String]) -> HTTPStubsTestBlock { - return { request in - guard let payload = request.payload else { return false } - let entries = parameters.filter { (key, _) in payload.contains { (name, _) in key == name } } - return entries.count == parameters.count && entries.reduce(true, { (initial, entry) -> Bool in - return initial && payload[entry.0] as? String == entry.1 - }) - } -} - -func hasUserMetadata(_ metadata: [String: String]) -> HTTPStubsTestBlock { - return hasObjectAttribute("user_metadata", value: metadata) -} - -func hasObjectAttribute(_ name: String, value: [String: String]) -> HTTPStubsTestBlock { - return { request in - guard let payload = request.payload, let actualValue = payload[name] as? [String: Any] else { return false } - return value.count == actualValue.count && value.reduce(true, { (initial, entry) -> Bool in - guard let value = actualValue[entry.0] as? String else { return false } - return initial && value == entry.1 - }) - } -} - -func hasNoneOf(_ names: [String]) -> HTTPStubsTestBlock { - return { request in - guard let payload = request.payload else { return false } - return payload.filter { names.contains($0.0) }.isEmpty - } -} - -func hasQueryParameters(_ parameters: [String: String]) -> HTTPStubsTestBlock { - return { request in - guard - let url = request.url, - let components = URLComponents(url: url, resolvingAgainstBaseURL: true), - let items = components.queryItems - else { return false } - return items.count == parameters.count && items.reduce(true, { (initial, item) -> Bool in - return initial && parameters[item.name] == item.value - }) - } -} - -func isToken(_ domain: String) -> HTTPStubsTestBlock { - return isMethodPOST() && isHost(domain) && isPath("/oauth/token") -} - -func isSignUp(_ domain: String) -> HTTPStubsTestBlock { - return isMethodPOST() && isHost(domain) && isPath("/dbconnections/signup") -} - -func isResetPassword(_ domain: String) -> HTTPStubsTestBlock { - return isMethodPOST() && isHost(domain) && isPath("/dbconnections/change_password") -} - -func isPasswordless(_ domain: String) -> HTTPStubsTestBlock { - return isMethodPOST() && isHost(domain) && isPath("/passwordless/start") -} - -func isUserInfo(_ domain: String) -> HTTPStubsTestBlock { - return isMethodGET() && isHost(domain) && isPath("/userinfo") -} - -func isRevokeToken(_ domain: String) -> HTTPStubsTestBlock { - return isMethodPOST() && isHost(domain) && isPath("/oauth/revoke") -} - -func isUsersPath(_ domain: String, identifier: String? = nil) -> HTTPStubsTestBlock { - let path: String - if let identifier = identifier { - path = "/api/v2/users/\(identifier)" - } else { - path = "/api/v2/users/" - } - return isHost(domain) && isPath(path) -} - -func isLinkPath(_ domain: String, identifier: String) -> HTTPStubsTestBlock { - return isHost(domain) && isPath("/api/v2/users/\(identifier)/identities") -} - -func isUnlinkPath(_ domain: String, identifier: String, provider: String, identityId: String) -> HTTPStubsTestBlock { - return isHost(domain) && isPath("/api/v2/users/\(identifier)/identities/\(provider)/\(identityId)") -} - -func isJWKSPath(_ domain: String) -> HTTPStubsTestBlock { - return isHost(domain) && isPath("/.well-known/jwks.json") -} - -func isMultifactorChallenge(_ domain: String) -> HTTPStubsTestBlock { - return isMethodPOST() && isHost(domain) && isPath("/mfa/challenge") -} - -func hasHeader(_ name: String, value: String) -> HTTPStubsTestBlock { - return { request in - return request.value(forHTTPHeaderField: name) == value - } -} - -func hasBearerToken(_ token: String) -> HTTPStubsTestBlock { - return hasHeader("Authorization", value: "Bearer \(token)") -} - -func containItem(withName name: String, value: String? = nil) -> Nimble.Predicate<[URLQueryItem]> { - return Predicate<[URLQueryItem]>.define("contain item with name <\(name)>") { expression, failureMessage -> PredicateResult in - guard let items = try expression.evaluate() else { return PredicateResult(status: .doesNotMatch, message: failureMessage) } +func containItem(withName name: String, value: String? = nil) -> Nimble.Matcher<[URLQueryItem]> { + return Matcher<[URLQueryItem]>.define("contain item with name <\(name)>") { expression, failureMessage -> MatcherResult in + guard let items = try expression.evaluate() else { return MatcherResult(status: .doesNotMatch, message: failureMessage) } let outcome = items.contains { item -> Bool in return item.name == name && ((value == nil && item.value != nil) || item.value == value) } - return PredicateResult(bool: outcome, message: failureMessage) + return MatcherResult(bool: outcome, message: failureMessage) } } -func haveAuthenticationError(code: String, description: String) -> Nimble.Predicate> { - return Predicate>.define("be an error response with code <\(code)> and description <\(description)") { expression, failureMessage -> PredicateResult in +func haveAuthenticationError(code: String, description: String) -> Nimble.Matcher> { + return Matcher>.define("be an error response with code <\(code)> and description <\(description)") { expression, failureMessage -> MatcherResult in return try beUnsuccessful(expression, failureMessage) { (error: AuthenticationError) -> Bool in return code == error.code && description == error.localizedDescription } @@ -139,8 +22,8 @@ func haveAuthenticationError(code: String, description: String) -> Nimble.Pre } #if WEB_AUTH_PLATFORM -func haveAuthenticationError(code: String, description: String) -> Nimble.Predicate> { - return Predicate>.define("be an error response with code <\(code)> and description <\(description)") { expression, failureMessage -> PredicateResult in +func haveAuthenticationError(code: String, description: String) -> Nimble.Matcher> { + return Matcher>.define("be an error response with code <\(code)> and description <\(description)") { expression, failureMessage -> MatcherResult in return try beUnsuccessful(expression, failureMessage) { (error: WebAuthError) -> Bool in guard let cause = error.cause as? AuthenticationError else { return false } return code == cause.code && description == cause.localizedDescription @@ -149,30 +32,30 @@ func haveAuthenticationError(code: String, description: String) -> Nimble.Pre } #endif -func haveManagementError(_ errorString: String, description: String, code: String, statusCode: Int) -> Nimble.Predicate> { - return Predicate>.define("be an error response with code <\(code)> and description <\(description)") { expression, failureMessage -> PredicateResult in +func haveManagementError(_ errorString: String, description: String, code: String, statusCode: Int) -> Nimble.Matcher> { + return Matcher>.define("be an error response with code <\(code)> and description <\(description)") { expression, failureMessage -> MatcherResult in return try beUnsuccessful(expression, failureMessage) { (error: ManagementError) -> Bool in return errorString == error.info["error"] as? String - && code == error.code - && description == error.localizedDescription - && statusCode == error.statusCode + && code == error.code + && description == error.localizedDescription + && statusCode == error.statusCode } } } -func haveManagementError(description: String, code: String, statusCode: Int = 0, cause: Error? = nil) -> Nimble.Predicate> { - return Predicate>.define("be an error response with code <\(code)> and description <\(description)") { expression, failureMessage -> PredicateResult in +func haveManagementError(description: String, code: String, statusCode: Int = 0, cause: Error? = nil) -> Nimble.Matcher> { + return Matcher>.define("be an error response with code <\(code)> and description <\(description)") { expression, failureMessage -> MatcherResult in return try beUnsuccessful(expression, failureMessage) { (error: ManagementError) -> Bool in return code == error.code - && description == error.localizedDescription - && statusCode == error.statusCode - && (cause == nil || error.cause?.localizedDescription == cause?.localizedDescription) + && description == error.localizedDescription + && statusCode == error.statusCode + && (cause == nil || error.cause?.localizedDescription == cause?.localizedDescription) } } } -func haveManagementError(description: String, statusCode: Int) -> Nimble.Predicate> { - return Predicate>.define("be an error result") { expression, failureMessage -> PredicateResult in +func haveManagementError(description: String, statusCode: Int) -> Nimble.Matcher> { + return Matcher>.define("be an error result") { expression, failureMessage -> MatcherResult in return try beUnsuccessful(expression, failureMessage) { (error: ManagementError) -> Bool in return error.localizedDescription == description && error.statusCode == statusCode } @@ -180,27 +63,27 @@ func haveManagementError(description: String, statusCode: Int) -> Nimble.Pred } #if WEB_AUTH_PLATFORM -func haveWebAuthError(_ expected: WebAuthError) -> Nimble.Predicate> { - return Predicate>.define("be an error result") { expression, failureMessage -> PredicateResult in +func haveWebAuthError(_ expected: WebAuthError) -> Nimble.Matcher> { + return Matcher>.define("be an error result") { expression, failureMessage -> MatcherResult in return try beUnsuccessful(expression, failureMessage) { (error: WebAuthError) -> Bool in return error == expected - && (expected.cause == nil || error.cause?.localizedDescription == expected.cause?.localizedDescription) + && (expected.cause == nil || error.cause?.localizedDescription == expected.cause?.localizedDescription) } } } #endif -func haveCredentialsManagerError(_ expected: CredentialsManagerError) -> Nimble.Predicate> { - return Predicate>.define("be an error result") { expression, failureMessage -> PredicateResult in +func haveCredentialsManagerError(_ expected: CredentialsManagerError) -> Nimble.Matcher> { + return Matcher>.define("be an error result") { expression, failureMessage -> MatcherResult in return try beUnsuccessful(expression, failureMessage) { (error: CredentialsManagerError) -> Bool in return error == expected - && (expected.cause == nil || error.cause?.localizedDescription == expected.cause?.localizedDescription) + && (expected.cause == nil || error.cause?.localizedDescription == expected.cause?.localizedDescription) } } } -func haveCredentials(_ accessToken: String? = nil, _ idToken: String? = nil) -> Nimble.Predicate> { - return Predicate>.define("be a successful authentication result") { expression, failureMessage -> PredicateResult in +func haveCredentials(_ accessToken: String? = nil, _ idToken: String? = nil) -> Nimble.Matcher> { + return Matcher>.define("be a successful authentication result") { expression, failureMessage -> MatcherResult in return try haveCredentials(accessToken: accessToken, idToken: idToken, refreshToken: nil, @@ -210,8 +93,8 @@ func haveCredentials(_ accessToken: String? = nil, _ idToken: String? = nil) -> } #if WEB_AUTH_PLATFORM -func haveCredentials(_ accessToken: String? = nil, _ idToken: String? = nil) -> Nimble.Predicate> { - return Predicate>.define("be a successful authentication result") { expression, failureMessage -> PredicateResult in +func haveCredentials(_ accessToken: String? = nil, _ idToken: String? = nil) -> Nimble.Matcher> { + return Matcher>.define("be a successful authentication result") { expression, failureMessage -> MatcherResult in return try haveCredentials(accessToken: accessToken, idToken: idToken, refreshToken: nil, @@ -221,8 +104,8 @@ func haveCredentials(_ accessToken: String? = nil, _ idToken: String? = nil) -> } #endif -func haveCredentials(_ accessToken: String, _ idToken: String? = nil, _ refreshToken: String? = nil) -> Nimble.Predicate> { - return Predicate>.define("be a successful credentials retrieval") { expression, failureMessage -> PredicateResult in +func haveCredentials(_ accessToken: String, _ idToken: String? = nil, _ refreshToken: String? = nil) -> Nimble.Matcher> { + return Matcher>.define("be a successful credentials retrieval") { expression, failureMessage -> MatcherResult in return try haveCredentials(accessToken: accessToken, idToken: idToken, refreshToken: refreshToken, @@ -231,48 +114,48 @@ func haveCredentials(_ accessToken: String, _ idToken: String? = nil, _ refreshT } } -func haveCredentials() -> Nimble.Predicate> { - return Predicate>.define("be a successful credentials retrieval") { expression, failureMessage -> PredicateResult in +func haveCredentials() -> Nimble.Matcher> { + return Matcher>.define("be a successful credentials retrieval") { expression, failureMessage -> MatcherResult in return try beSuccessful(expression, failureMessage) } } -func haveCreatedUser(_ email: String, username: String? = nil) -> Nimble.Predicate> { - return Predicate>.define("have created user with email <\(email)>") { expression, failureMessage -> PredicateResult in +func haveCreatedUser(_ email: String, username: String? = nil) -> Nimble.Matcher> { + return Matcher>.define("have created user with email <\(email)>") { expression, failureMessage -> MatcherResult in return try beSuccessful(expression, failureMessage) { (created: DatabaseUser) -> Bool in return created.email == email && (username == nil || created.username == username) } } } -func beSuccessful() -> Nimble.Predicate> { - return Predicate>.define("be a successful result") { expression, failureMessage -> PredicateResult in +func beSuccessful() -> Nimble.Matcher> { + return Matcher>.define("be a successful result") { expression, failureMessage -> MatcherResult in return try beSuccessful(expression, failureMessage) } } -func beSuccessful() -> Nimble.Predicate> { - return Predicate>.define("be a successful result") { expression, failureMessage -> PredicateResult in +func beSuccessful() -> Nimble.Matcher> { + return Matcher>.define("be a successful result") { expression, failureMessage -> MatcherResult in return try beSuccessful(expression, failureMessage) } } #if WEB_AUTH_PLATFORM -func beSuccessful() -> Nimble.Predicate> { - return Predicate>.define("be a successful result") { expression, failureMessage -> PredicateResult in +func beSuccessful() -> Nimble.Matcher> { + return Matcher>.define("be a successful result") { expression, failureMessage -> MatcherResult in return try beSuccessful(expression, failureMessage) } } #endif -func beSuccessful() -> Nimble.Predicate> { - return Predicate>.define("be a successful result") { expression, failureMessage -> PredicateResult in +func beSuccessful() -> Nimble.Matcher> { + return Matcher>.define("be a successful result") { expression, failureMessage -> MatcherResult in return try beSuccessful(expression, failureMessage) } } -func beUnsuccessful(_ cause: String? = nil) -> Nimble.Predicate> { - return Predicate>.define("be a failure result") { expression, failureMessage -> PredicateResult in +func beUnsuccessful(_ cause: String? = nil) -> Nimble.Matcher> { + return Matcher>.define("be a failure result") { expression, failureMessage -> MatcherResult in if let cause = cause { _ = failureMessage.appended(message: " with cause \(cause)") } else { @@ -283,8 +166,8 @@ func beUnsuccessful(_ cause: String? = nil) -> Nimble.Predicate(_ cause: String? = nil) -> Nimble.Predicate> { - return Predicate>.define("be a failure result") { expression, failureMessage -> PredicateResult in +func beUnsuccessful(_ cause: String? = nil) -> Nimble.Matcher> { + return Matcher>.define("be a failure result") { expression, failureMessage -> MatcherResult in if let cause = cause { _ = failureMessage.appended(message: " with cause \(cause)") } else { @@ -295,21 +178,21 @@ func beUnsuccessful(_ cause: String? = nil) -> Nimble.Predicate() -> Nimble.Predicate> { - return Predicate>.define("be a failure result") { expression, failureMessage -> PredicateResult in +func beUnsuccessful() -> Nimble.Matcher> { + return Matcher>.define("be a failure result") { expression, failureMessage -> MatcherResult in _ = failureMessage.appended(message: " from credentials manager") return try beUnsuccessful(expression, failureMessage) } } -func haveProfile(_ sub: String) -> Nimble.Predicate> { - return Predicate>.define("have userInfo for sub: <\(sub)>") { expression, failureMessage -> PredicateResult in +func haveProfile(_ sub: String) -> Nimble.Matcher> { + return Matcher>.define("have userInfo for sub: <\(sub)>") { expression, failureMessage -> MatcherResult in return try beSuccessful(expression, failureMessage) { (userInfo: UserInfo) -> Bool in userInfo.sub == sub } } } -func haveObjectWithAttributes(_ attributes: [String]) -> Nimble.Predicate> { - return Predicate>.define("have attributes \(attributes)") { expression, failureMessage -> PredicateResult in +func haveObjectWithAttributes(_ attributes: [String]) -> Nimble.Matcher> { + return Matcher>.define("have attributes \(attributes)") { expression, failureMessage -> MatcherResult in return try beSuccessful(expression, failureMessage) { (value: [String: Any]) -> Bool in return Array(value.keys).reduce(true, { (initial, value) -> Bool in return initial && attributes.contains(value) @@ -318,22 +201,22 @@ func haveObjectWithAttributes(_ attributes: [String]) -> Nimble.Predicate Nimble.Predicate> { - return Predicate>.define("have a JWKS object with at least one key") { expression, failureMessage -> PredicateResult in +func haveJWKS() -> Nimble.Matcher> { + return Matcher>.define("have a JWKS object with at least one key") { expression, failureMessage -> MatcherResult in return try beSuccessful(expression, failureMessage) { (jwks: JWKS) -> Bool in !jwks.keys.isEmpty } } } -func beURLSafeBase64() -> Nimble.Predicate { - return Predicate.define("be url safe base64") { expression, failureMessage -> PredicateResult in +func beURLSafeBase64() -> Nimble.Matcher { + return Matcher.define("be url safe base64") { expression, failureMessage -> MatcherResult in var set = CharacterSet() set.formUnion(.alphanumerics) set.insert(charactersIn: "-_/") set.invert() if let actual = try expression.evaluate() , actual.rangeOfCharacter(from: set) == nil { - return PredicateResult(status: .matches, message: failureMessage) + return MatcherResult(status: .matches, message: failureMessage) } - return PredicateResult(status: .doesNotMatch, message: failureMessage) + return MatcherResult(status: .doesNotMatch, message: failureMessage) } } @@ -341,27 +224,27 @@ func beURLSafeBase64() -> Nimble.Predicate { private func beSuccessful(_ expression: Expression>, _ message: ExpectationMessage, - predicate: @escaping (T) -> Bool = { _ in true }) throws -> PredicateResult { + predicate: @escaping (T) -> Bool = { _ in true }) throws -> MatcherResult { if let actual = try expression.evaluate(), case .success(let value) = actual { - return PredicateResult(bool: predicate(value), message: message) + return MatcherResult(bool: predicate(value), message: message) } - return PredicateResult(status: .doesNotMatch, message: message) + return MatcherResult(status: .doesNotMatch, message: message) } private func beUnsuccessful(_ expression: Expression>, - _ message: ExpectationMessage, - predicate: @escaping (E) -> Bool = { _ in true }) throws -> PredicateResult { + _ message: ExpectationMessage, + predicate: @escaping (E) -> Bool = { _ in true }) throws -> MatcherResult { if let actual = try expression.evaluate(), case .failure(let error) = actual { - return PredicateResult(bool: predicate(error), message: message) + return MatcherResult(bool: predicate(error), message: message) } - return PredicateResult(status: .doesNotMatch, message: message) + return MatcherResult(status: .doesNotMatch, message: message) } private func haveCredentials(accessToken: String?, idToken: String?, refreshToken: String?, _ expression: Expression>, - _ message: ExpectationMessage) throws -> PredicateResult { + _ message: ExpectationMessage) throws -> MatcherResult { if let accessToken = accessToken { _ = message.appended(message: " ") } @@ -373,17 +256,143 @@ private func haveCredentials(accessToken: String?, } return try beSuccessful(expression, message) { (credentials: Credentials) -> Bool in return (accessToken == nil || credentials.accessToken == accessToken) - && (idToken == nil || credentials.idToken == idToken) - && (refreshToken == nil || credentials.refreshToken == refreshToken) + && (idToken == nil || credentials.idToken == idToken) + && (refreshToken == nil || credentials.refreshToken == refreshToken) } } // MARK: - Extensions extension URLRequest { - + var payload: [String: Any]? { return URLProtocol.property(forKey: parameterPropertyKey, in: self) as? [String: Any] } - + + var isMethodPOST: Bool { + return httpMethod == "POST" + } + + var isMethodGET: Bool { + return httpMethod == "GET" + } + + var isMethodPATCH: Bool { + return httpMethod == "PATCH" + } + + var isMethodDELETE: Bool { + return httpMethod == "DELETE" + } + + func isHost(_ host: String) -> Bool { + precondition(!host.contains("/"), "The host part of a URL never contains any slash. Only use strings like 'api.example.com' for this value, and not things like 'https://api.example.com/'") + return url?.host == host + } + + func isPath(_ path: String) -> Bool { + return url?.path == path + } + + func isToken(_ domain: String) -> Bool { + return isMethodPOST && isHost(domain) && isPath("/oauth/token") + } + + func isSignUp(_ domain: String) -> Bool { + return isMethodPOST && isHost(domain) && isPath("/dbconnections/signup") + } + + func isResetPassword(_ domain: String) -> Bool { + return isMethodPOST && isHost(domain) && isPath("/dbconnections/change_password") + } + + func isPasswordless(_ domain: String) -> Bool { + return isMethodPOST && isHost(domain) && isPath("/passwordless/start") + } + + func isUserInfo(_ domain: String) -> Bool { + return isMethodGET && isHost(domain) && isPath("/userinfo") + } + + func isRevokeToken(_ domain: String) -> Bool { + return isMethodPOST && isHost(domain) && isPath("/oauth/revoke") + } + + func isUsersPath(_ domain: String, identifier: String? = nil) -> Bool { + let path: String + if let identifier = identifier { + path = "/api/v2/users/\(identifier)" + } else { + path = "/api/v2/users/" + } + return isHost(domain) && isPath(path) + } + + + func isLinkPath(_ domain: String, identifier: String) -> Bool { + return isHost(domain) && isPath("/api/v2/users/\(identifier)/identities") + } + + func isUnlinkPath(_ domain: String, identifier: String, provider: String, identityId: String) -> Bool { + return isHost(domain) && isPath("/api/v2/users/\(identifier)/identities/\(provider)/\(identityId)") + } + + func isJWKSPath(_ domain: String) -> Bool { + return isHost(domain) && isPath("/.well-known/jwks.json") + } + + func isMultifactorChallenge(_ domain: String) -> Bool { + return isMethodPOST && isHost(domain) && isPath("/mfa/challenge") + } + + func hasHeader(_ name: String, value: String) -> Bool { + return self.value(forHTTPHeaderField: name) == value + } + + func hasBearerToken(_ token: String) -> Bool { + return hasHeader("Authorization", value: "Bearer \(token)") + } + + func hasAllOf(_ parameters: [String: String]) -> Bool { + guard let payload = self.payload else { return false } + return parameters.count == payload.count && parameters.reduce(true, { (initial, entry) -> Bool in + return initial && payload[entry.0] as? String == entry.1 + }) + } + + func hasAtLeast(_ parameters: [String: String]) -> Bool { + guard let payload = self.payload else { return false } + let entries = parameters.filter { (key, _) in payload.contains { (name, _) in key == name } } + return entries.count == parameters.count && entries.reduce(true, { (initial, entry) -> Bool in + return initial && payload[entry.0] as? String == entry.1 + }) + } + + func hasUserMetadata(_ metadata: [String: String]) -> Bool { + return hasObjectAttribute("user_metadata", value: metadata) + } + + func hasObjectAttribute(_ name: String, value: [String: String]) -> Bool { + guard let payload = self.payload, let actualValue = payload[name] as? [String: Any] else { return false } + return value.count == actualValue.count && value.reduce(true, { (initial, entry) -> Bool in + guard let value = actualValue[entry.0] as? String else { return false } + return initial && value == entry.1 + }) + } + + func hasNoneOf(_ names: [String]) -> Bool { + guard let payload = self.payload else { return false } + return payload.filter { names.contains($0.0) }.isEmpty + } + + func hasQueryParameters(_ parameters: [String: String]) -> Bool { + guard + let url = self.url, + let components = URLComponents(url: url, resolvingAgainstBaseURL: true), + let items = components.queryItems + else { return false } + return items.count == parameters.count && items.reduce(true, { (initial, item) -> Bool in + return initial && parameters[item.name] == item.value + }) + } } diff --git a/Auth0Tests/NetworkStub.swift b/Auth0Tests/NetworkStub.swift new file mode 100644 index 00000000..8fc99e52 --- /dev/null +++ b/Auth0Tests/NetworkStub.swift @@ -0,0 +1,24 @@ +// +// NetworkStub.swift +// Auth0 +// +// Created by Desu Sai Venkat on 26/06/24. +// Copyright © 2024 Auth0. All rights reserved. +// + +import Foundation + +typealias RequestCondition = (URLRequest) -> Bool +typealias RequestResponse = (URLRequest) -> (Data?, URLResponse?, Error?) + +struct NetworkStub { + static private(set) var stubs: [(condition: RequestCondition, response: RequestResponse)] = [] + + static func addStub(condition: @escaping RequestCondition, response: @escaping RequestResponse) { + stubs.append((condition, response)) + } + + static func clearStubs() { + stubs.removeAll() + } +} diff --git a/Auth0Tests/OAuth2GrantSpec.swift b/Auth0Tests/OAuth2GrantSpec.swift index 6f7bf6be..d32e944c 100644 --- a/Auth0Tests/OAuth2GrantSpec.swift +++ b/Auth0Tests/OAuth2GrantSpec.swift @@ -1,15 +1,12 @@ import Quick import Nimble -import OHHTTPStubs -#if SWIFT_PACKAGE -import OHHTTPStubsSwift -#endif +import Foundation @testable import Auth0 class OAuth2GrantSpec: QuickSpec { - override func spec() { + override class func spec() { let domain = URL.httpsURL(from: "samples.auth0.com") let authentication = Auth0Authentication(clientId: "CLIENT_ID", url: domain) @@ -19,11 +16,12 @@ class OAuth2GrantSpec: QuickSpec { let leeway = 60 * 1000 beforeEach { - stub(condition: isHost(domain.host!)) { _ in catchAllResponse() }.name = "YOU SHALL NOT PASS!" + URLProtocol.registerClass(StubURLProtocol.self) } afterEach { - HTTPStubs.removeAllStubs() + NetworkStub.clearStubs() + URLProtocol.unregisterClass(StubURLProtocol.self) } describe("Authorization Code w/PKCE") { @@ -45,11 +43,13 @@ class OAuth2GrantSpec: QuickSpec { let token = UUID().uuidString let code = UUID().uuidString let values = ["code": code] - stub(condition: isToken(domain.host!) && hasAtLeast(["code": code, "code_verifier": pkce.verifier, "grant_type": "authorization_code", "redirect_uri": pkce.redirectURL.absoluteString])) { _ in - return authResponse(accessToken: token, idToken: idToken) - }.name = "Code Exchange Auth" - stub(condition: isJWKSPath(domain.host!)) { _ in jwksResponse() } - await waitUntil { done in + NetworkStub.addStub(condition: { + $0.isToken(domain.host!) && $0.hasAtLeast(["code": code, "code_verifier": pkce.verifier, "grant_type": "authorization_code", "redirect_uri": pkce.redirectURL.absoluteString]) + }, response: authResponse(accessToken: token, idToken: idToken)) + NetworkStub.addStub(condition: { + $0.isJWKSPath(domain.host!) + }, response: jwksResponse()) + waitUntil { done in pkce.credentials(from: values) { expect($0).to(haveCredentials(token)) done() @@ -58,7 +58,7 @@ class OAuth2GrantSpec: QuickSpec { } it("shoud report error to get credentials") { - await waitUntil { done in + waitUntil { done in pkce.credentials(from: [:]) { expect($0).to(beUnsuccessful()) done() @@ -111,9 +111,13 @@ class OAuth2GrantSpec: QuickSpec { let token = UUID().uuidString let code = UUID().uuidString let values = ["code": code, "nonce": nonce] - stub(condition: isToken(domain.host!) && hasAtLeast(["code": code, "code_verifier": pkce.verifier, "grant_type": "authorization_code", "redirect_uri": pkce.redirectURL.absoluteString])) { _ in return authResponse(accessToken: token, idToken: idToken) }.name = "Code Exchange Auth" - stub(condition: isJWKSPath(domain.host!)) { _ in jwksResponse() } - await waitUntil { done in + NetworkStub.addStub(condition: { + $0.isToken(domain.host!) && $0.hasAtLeast(["code": code, "code_verifier": pkce.verifier, "grant_type": "authorization_code", "redirect_uri": pkce.redirectURL.absoluteString]) + }, response: authResponse(accessToken: token, idToken: idToken)) + NetworkStub.addStub(condition: { + $0.isJWKSPath(domain.host!) + }, response: jwksResponse()) + waitUntil { done in pkce.credentials(from: values) { expect($0).to(haveCredentials(token)) done() @@ -128,9 +132,13 @@ class OAuth2GrantSpec: QuickSpec { let values = ["code": code, "nonce": nonce] let idToken = generateJWT(iss: nil, nonce: nonce).string let expectedError = WebAuthError(code: .idTokenValidationFailed, cause: IDTokenIssValidator.ValidationError.missingIss) - stub(condition: isToken(domain.host!) && hasAtLeast(["code": code, "code_verifier": pkce.verifier, "grant_type": "authorization_code", "redirect_uri": pkce.redirectURL.absoluteString])) { _ in return authResponse(accessToken: token, idToken: idToken) }.name = "Code Exchange Auth" - stub(condition: isJWKSPath(domain.host!)) { _ in jwksResponse() } - await waitUntil { done in + NetworkStub.addStub(condition: { + $0.isToken(domain.host!) && $0.hasAtLeast(["code": code, "code_verifier": pkce.verifier, "grant_type": "authorization_code", "redirect_uri": pkce.redirectURL.absoluteString]) + }, response: authResponse(accessToken: token, idToken: idToken)) + NetworkStub.addStub(condition: { + $0.isJWKSPath(domain.host!) + }, response: jwksResponse()) + waitUntil { done in pkce.credentials(from: values) { expect($0).to(haveWebAuthError(expectedError)) done() @@ -143,10 +151,10 @@ class OAuth2GrantSpec: QuickSpec { let code = UUID().uuidString let values = ["code": code, "nonce": nonce] let expectedError = WebAuthError(code: .pkceNotAllowed) - stub(condition: isToken(domain.host!) && hasAtLeast(["code": code, "code_verifier": pkce.verifier, "grant_type": "authorization_code", "redirect_uri": pkce.redirectURL.absoluteString])) { _ in - return authFailure(error: "foo", description: "Unauthorized") - }.name = "Failed Code Exchange Auth" - await waitUntil { done in + NetworkStub.addStub(condition: { + $0.isToken(domain.host!) && $0.hasAtLeast(["code": code, "code_verifier": pkce.verifier, "grant_type": "authorization_code", "redirect_uri": pkce.redirectURL.absoluteString]) + }, response: authFailure(error: "foo", description: "Unauthorized")) + waitUntil { done in pkce.credentials(from: values) { expect($0).to(haveWebAuthError(expectedError)) done() @@ -162,10 +170,10 @@ class OAuth2GrantSpec: QuickSpec { let errorDescription = "bar" let cause = AuthenticationError(info: ["error": errorCode, "error_description": errorDescription]) let expectedError = WebAuthError(code: .other, cause: cause) - stub(condition: isToken(domain.host!) && hasAtLeast(["code": code, "code_verifier": pkce.verifier, "grant_type": "authorization_code", "redirect_uri": pkce.redirectURL.absoluteString])) { _ in - return authFailure(error: errorCode, description: errorDescription) - }.name = "Failed Code Exchange Auth" - await waitUntil { done in + NetworkStub.addStub(condition: { + $0.isToken(domain.host!) && $0.hasAtLeast(["code": code, "code_verifier": pkce.verifier, "grant_type": "authorization_code", "redirect_uri": pkce.redirectURL.absoluteString]) + }, response: authFailure(error: errorCode, description: errorDescription)) + waitUntil { done in pkce.credentials(from: values) { expect($0).to(haveWebAuthError(expectedError)) done() @@ -174,7 +182,7 @@ class OAuth2GrantSpec: QuickSpec { } it("shoud report error to get credentials") { - await waitUntil { done in + waitUntil { done in pkce.credentials(from: [:]) { expect($0).to(beUnsuccessful()) done() diff --git a/Auth0Tests/RequestSpec.swift b/Auth0Tests/RequestSpec.swift index 4eb402ba..a4a16044 100644 --- a/Auth0Tests/RequestSpec.swift +++ b/Auth0Tests/RequestSpec.swift @@ -2,10 +2,6 @@ import Foundation import Combine import Quick import Nimble -import OHHTTPStubs -#if SWIFT_PACKAGE -import OHHTTPStubsSwift -#endif @testable import Auth0 @@ -32,14 +28,15 @@ fileprivate extension Request where T == [String: Any], E == AuthenticationError } class RequestSpec: QuickSpec { - override func spec() { + override class func spec() { beforeEach { - stub(condition: isHost(Url.host!)) { _ in catchAllResponse() }.name = "YOU SHALL NOT PASS!" + URLProtocol.registerClass(StubURLProtocol.self) } afterEach { - HTTPStubs.removeAllStubs() + NetworkStub.clearStubs() + URLProtocol.unregisterClass(StubURLProtocol.self) } describe("create and update request") { @@ -144,11 +141,11 @@ class RequestSpec: QuickSpec { } it("should emit only one value") { - stub(condition: isHost(Url.host!)) { _ in - return apiSuccessResponse() - } + NetworkStub.addStub(condition: { + $0.isHost(Url.host!) + }, response: apiSuccessResponse()) let request = Request() - await waitUntil(timeout: Timeout) { done in + waitUntil(timeout: Timeout) { done in request .start() .assertNoFailure() @@ -162,11 +159,11 @@ class RequestSpec: QuickSpec { } it("should complete with the response") { - stub(condition: isHost(Url.host!)) { _ in - return apiSuccessResponse(json: ["foo": "bar"]) - } + NetworkStub.addStub(condition: { + $0.isHost(Url.host!) + }, response: apiSuccessResponse(json: ["foo": "bar"])) let request = Request() - await waitUntil(timeout: Timeout) { done in + waitUntil(timeout: Timeout) { done in request .start() .sink(receiveCompletion: { completion in @@ -180,11 +177,11 @@ class RequestSpec: QuickSpec { } it("should complete with an error") { - stub(condition: isHost(Url.host!)) { _ in - return apiFailureResponse() - } + NetworkStub.addStub(condition: { + $0.isHost(Url.host!) + }, response: apiFailureResponse()) let request = Request() - await waitUntil(timeout: Timeout) { done in + waitUntil(timeout: Timeout) { done in request .start() .ignoreOutput() @@ -202,11 +199,11 @@ class RequestSpec: QuickSpec { describe("async await") { it("should return the response") { - stub(condition: isHost(Url.host!)) { _ in - return apiSuccessResponse(json: ["foo": "bar"]) - } + NetworkStub.addStub(condition: { + $0.isHost(Url.host!) + }, response: apiSuccessResponse(json: ["foo": "bar"])) let request = Request() - await waitUntil(timeout: Timeout) { done in + waitUntil(timeout: Timeout) { done in Task.init { let response = try await request.start() expect(response).toNot(beEmpty()) @@ -216,11 +213,11 @@ class RequestSpec: QuickSpec { } it("should throw an error") { - stub(condition: isHost(Url.host!)) { _ in - return apiFailureResponse() - } + NetworkStub.addStub(condition: { + $0.isHost(Url.host!) + }, response: apiFailureResponse()) let request = Request() - await waitUntil(timeout: Timeout) { done in + waitUntil(timeout: Timeout) { done in Task.init { do { _ = try await request.start() diff --git a/Auth0Tests/ResponseSpec.swift b/Auth0Tests/ResponseSpec.swift index 71a749f1..ad314228 100644 --- a/Auth0Tests/ResponseSpec.swift +++ b/Auth0Tests/ResponseSpec.swift @@ -12,7 +12,7 @@ private func http(_ statusCode: Int, url: Foundation.URL = URL) -> HTTPURLRespon } class ResponseSpec: QuickSpec { - override func spec() { + override class func spec() { describe("successful response") { diff --git a/Auth0Tests/Responses.swift b/Auth0Tests/Responses.swift index 6c8c2cfe..877930f7 100644 --- a/Auth0Tests/Responses.swift +++ b/Auth0Tests/Responses.swift @@ -1,5 +1,4 @@ import Foundation -import OHHTTPStubs @testable import Auth0 @@ -28,34 +27,57 @@ let RecoveryCode = "162534" let MFAToken = UUID().uuidString.replacingOccurrences(of: "-", with: "") let AuthenticatorId = UUID().uuidString.replacingOccurrences(of: "-", with: "") let ChallengeTypes = ["oob", "otp"] -let APISuccessStatusCode = Int32(200) +let APISuccessStatusCode = 200 let APIResponseHeaders = ["Content-Type": "application/json"] -func catchAllResponse() -> HTTPStubsResponse { - return HTTPStubsResponse(error: NSError(domain: "com.auth0", code: -99999, userInfo: nil)) +func catchAllResponse() -> RequestResponse { + return { request in + let error = NSError(domain: "com.auth0", code: -99999, userInfo: nil) + return (nil, nil, error) + } } -func apiSuccessResponse(json: [AnyHashable: Any] = [:]) -> HTTPStubsResponse { - return HTTPStubsResponse(jsonObject: json, statusCode: APISuccessStatusCode, headers: APIResponseHeaders) +func apiSuccessResponse(json: [AnyHashable: Any] = [:]) -> RequestResponse { + return { request in + let data = try! JSONSerialization.data(withJSONObject: json, options: []) + let response = HTTPURLResponse(url: request.url!, statusCode: APISuccessStatusCode, httpVersion: nil, headerFields: APIResponseHeaders)! + return (data, response, nil) + } } -func apiSuccessResponse(jsonArray: [Any]) -> HTTPStubsResponse { - return HTTPStubsResponse(jsonObject: jsonArray, statusCode: APISuccessStatusCode, headers: APIResponseHeaders) +func apiSuccessResponse(jsonArray: [Any]) -> RequestResponse { + return { request in + let data = try! JSONSerialization.data(withJSONObject: jsonArray, options: []) + let response = HTTPURLResponse(url: request.url!, statusCode: APISuccessStatusCode, httpVersion: nil, headerFields: APIResponseHeaders)! + return (data, response, nil) + } } -func apiSuccessResponse(string: String) -> HTTPStubsResponse { - return HTTPStubsResponse(data: string.data(using: .utf8)!, statusCode: APISuccessStatusCode, headers: APIResponseHeaders) +func apiSuccessResponse(string: String) -> RequestResponse { + return { request in + let data = string.data(using: .utf8) + let response = HTTPURLResponse(url: request.url!, statusCode: APISuccessStatusCode, httpVersion: nil, headerFields: APIResponseHeaders)! + return (data, response, nil) + } } -func apiFailureResponse(json: [AnyHashable: Any] = [:], statusCode: Int = 400) -> HTTPStubsResponse { - return HTTPStubsResponse(jsonObject: json, statusCode: Int32(statusCode), headers: APIResponseHeaders) +func apiFailureResponse(json: [AnyHashable: Any] = [:], statusCode: Int = 400) -> RequestResponse { + return { request in + let data = try! JSONSerialization.data(withJSONObject: json, options: []) + let response = HTTPURLResponse(url: request.url!, statusCode: statusCode, httpVersion: nil, headerFields: APIResponseHeaders)! + return (data, response, nil) + } } -func apiFailureResponse(string: String, statusCode: Int) -> HTTPStubsResponse { - return HTTPStubsResponse(data: string.data(using: .utf8)!, statusCode: Int32(statusCode), headers: APIResponseHeaders) +func apiFailureResponse(string: String, statusCode: Int) -> RequestResponse { + return { request in + let data = string.data(using: .utf8) + let response = HTTPURLResponse(url: request.url!, statusCode: statusCode, httpVersion: nil, headerFields: APIResponseHeaders)! + return (data, response, nil) + } } -func authResponse(accessToken: String, idToken: String? = nil, refreshToken: String? = nil, expiresIn: Double? = nil) -> HTTPStubsResponse { +func authResponse(accessToken: String, idToken: String? = nil, refreshToken: String? = nil, expiresIn: Double? = nil) -> RequestResponse { var json = [ "access_token": accessToken, "token_type": "bearer", @@ -75,15 +97,15 @@ func authResponse(accessToken: String, idToken: String? = nil, refreshToken: Str return apiSuccessResponse(json: json) } -func authFailure(code: String, description: String, name: String? = nil) -> HTTPStubsResponse { +func authFailure(code: String, description: String, name: String? = nil) -> RequestResponse { return apiFailureResponse(json: ["code": code, "description": description, "statusCode": 400, "name": name ?? code]) } -func authFailure(error: String, description: String) -> HTTPStubsResponse { +func authFailure(error: String, description: String) -> RequestResponse { return apiFailureResponse(json: ["error": error, "error_description": description]) } -func createdUser(email: String, username: String? = nil, verified: Bool = true) -> HTTPStubsResponse { +func createdUser(email: String, username: String? = nil, verified: Bool = true) -> RequestResponse { var json: [String: Any] = [ "email": email, "email_verified": verified ? "true" : "false", @@ -95,23 +117,23 @@ func createdUser(email: String, username: String? = nil, verified: Bool = true) return apiSuccessResponse(json: json) } -func resetPasswordResponse() -> HTTPStubsResponse { +func resetPasswordResponse() -> RequestResponse { return apiSuccessResponse(string: "We've just sent you an email to reset your password.") } -func revokeTokenResponse() -> HTTPStubsResponse { +func revokeTokenResponse() -> RequestResponse { return apiSuccessResponse(string: "") } -func passwordless(_ email: String, verified: Bool) -> HTTPStubsResponse { +func passwordless(_ email: String, verified: Bool) -> RequestResponse { return apiSuccessResponse(json: ["email": email, "verified": "\(verified)"]) } -func managementErrorResponse(error: String, description: String, code: String, statusCode: Int = 400) -> HTTPStubsResponse { +func managementErrorResponse(error: String, description: String, code: String, statusCode: Int = 400) -> RequestResponse { return apiFailureResponse(json: ["code": code, "description": description, "statusCode": statusCode, "error": error], statusCode: statusCode) } -func jwksResponse(kid: String? = Kid) -> HTTPStubsResponse { +func jwksResponse(kid: String? = Kid) -> RequestResponse { var jwks: [String: Any] = ["keys": [["alg": "RS256", "kty": "RSA", "use": "sig", @@ -132,7 +154,7 @@ func jwksResponse(kid: String? = Kid) -> HTTPStubsResponse { return apiSuccessResponse(json: jwks) } -func multifactorChallengeResponse(challengeType: String, oobCode: String? = nil, bindingMethod: String? = nil) -> HTTPStubsResponse { +func multifactorChallengeResponse(challengeType: String, oobCode: String? = nil, bindingMethod: String? = nil) -> RequestResponse { var json: [String: Any] = ["challenge_type": challengeType] json["oob_code"] = oobCode json["binding_method"] = bindingMethod diff --git a/Auth0Tests/SafariProviderSpec.swift b/Auth0Tests/SafariProviderSpec.swift index b796b52b..fb376d5e 100644 --- a/Auth0Tests/SafariProviderSpec.swift +++ b/Auth0Tests/SafariProviderSpec.swift @@ -12,37 +12,37 @@ private let Timeout: NimbleTimeInterval = .seconds(2) class SafariProviderSpec: QuickSpec { - override func spec() { + override class func spec() { var safari: SFSafariViewController! var userAgent: SafariUserAgent! - beforeEach { @MainActor in + beforeEach { safari = SFSafariViewController(url: RedirectURL) userAgent = SafariUserAgent(controller: safari, callback: { _ in }) } describe("WebAuthentication extension") { - it("should create a safari provider") { @MainActor in + it("should create a safari provider") { let provider = WebAuthentication.safariProvider() expect(provider(RedirectURL, { _ in })).to(beAKindOf(SafariUserAgent.self)) } - it("should use the fullscreen presentation style by default") { @MainActor in + it("should use the fullscreen presentation style by default") { let provider = WebAuthentication.safariProvider() let userAgent = provider(RedirectURL, { _ in }) as! SafariUserAgent expect(userAgent.controller.modalPresentationStyle) == .fullScreen } - it("should set a custom presentation style") { @MainActor in + it("should set a custom presentation style") { let style = UIModalPresentationStyle.formSheet let provider = WebAuthentication.safariProvider(style: style) let userAgent = provider(RedirectURL, { _ in }) as! SafariUserAgent expect(userAgent.controller.modalPresentationStyle) == style } - it("should use the cancel dismiss button style") { @MainActor in + it("should use the cancel dismiss button style") { let provider = WebAuthentication.safariProvider() let userAgent = provider(RedirectURL, { _ in }) as! SafariUserAgent expect(userAgent.controller.dismissButtonStyle) == .cancel @@ -54,33 +54,33 @@ class SafariProviderSpec: QuickSpec { var root: SpyViewController! - beforeEach { @MainActor in + beforeEach { root = SpyViewController() UIApplication.shared.windows.last(where: \.isKeyWindow)?.rootViewController = root } - it("should return nil when root is nil") { @MainActor in + it("should return nil when root is nil") { UIApplication.shared.windows.last(where: \.isKeyWindow)?.rootViewController = nil expect(safari.topViewController).to(beNil()) } - it("should return root when is top controller") { @MainActor in + it("should return root when is top controller") { expect(safari.topViewController) == root } - it("should return presented controller") { @MainActor in + it("should return presented controller") { let presented = UIViewController() root.presented = presented expect(safari.topViewController) == presented } - it("should return split view controller if contains nothing") { @MainActor in + it("should return split view controller if contains nothing") { let split = UISplitViewController() root.presented = split expect(safari.topViewController) == split } - it("should return last controller from split view controller") { @MainActor in + it("should return last controller from split view controller") { let split = UISplitViewController() let last = UIViewController() split.viewControllers = [UIViewController(), last] @@ -88,26 +88,26 @@ class SafariProviderSpec: QuickSpec { expect(safari.topViewController) == last } - it("should return navigation controller if contains nothing") { @MainActor in + it("should return navigation controller if contains nothing") { let navigation = UINavigationController() root.presented = navigation expect(safari.topViewController) == navigation } - it("should return top from navigation controller") { @MainActor in + it("should return top from navigation controller") { let top = UIViewController() let navigation = UINavigationController(rootViewController: top) root.presented = navigation expect(safari.topViewController) == top } - it("should return tab bar controller if contains nothing") { @MainActor in + it("should return tab bar controller if contains nothing") { let tabs = UITabBarController() root.presented = tabs expect(safari.topViewController) == tabs } - it("should return top from tab bar controller") { @MainActor in + it("should return top from tab bar controller") { let top = UIViewController() let tabs = UITabBarController() tabs.viewControllers = [top] @@ -118,21 +118,21 @@ class SafariProviderSpec: QuickSpec { describe("user agent") { - it("should have a custom description") { @MainActor in + it("should have a custom description") { let safari = SFSafariViewController(url: RedirectURL) let userAgent = SafariUserAgent(controller: safari, callback: { _ in }) expect(userAgent.description) == "SFSafariViewController" } - it("should be the safari view controller's delegate") { @MainActor in + it("should be the safari view controller's delegate") { expect(safari.delegate).to(be(userAgent)) } - it("should be the safari view controller's presentation delegate") { @MainActor in + it("should be the safari view controller's presentation delegate") { expect(safari.presentationController?.delegate).to(be(userAgent)) } - it("should present the safari view controller") { @MainActor in + it("should present the safari view controller") { let root = SpyViewController() UIApplication.shared.windows.last(where: \.isKeyWindow)?.rootViewController = root let safari = SpySafariViewController(url: RedirectURL) @@ -143,7 +143,7 @@ class SafariProviderSpec: QuickSpec { } it("should call the callback with an error when the user cancels the operation") { - await waitUntil(timeout: Timeout) { done in + waitUntil(timeout: Timeout) { done in DispatchQueue.main.async { [safari] in let userAgent = SafariUserAgent(controller: safari!, callback: { result in expect(result).to(haveWebAuthError(WebAuthError(code: .userCancelled))) @@ -157,7 +157,7 @@ class SafariProviderSpec: QuickSpec { it("should call the callback with an error when the safari view controller cannot be dismissed") { let expectedError = WebAuthError(code: .unknown("Cannot dismiss SFSafariViewController")) - await waitUntil(timeout: Timeout) { done in + waitUntil(timeout: Timeout) { done in DispatchQueue.main.async { [safari] in let userAgent = SafariUserAgent(controller: safari!, callback: { result in expect(result).to(haveWebAuthError(expectedError)) @@ -168,14 +168,14 @@ class SafariProviderSpec: QuickSpec { } } - it("should call the callback with success") { @MainActor in + it("should call the callback with success") { let root = UIViewController() let window = UIWindow(frame: CGRect()) window.rootViewController = root window.makeKeyAndVisible() root.present(safari, animated: false) - await waitUntil(timeout: Timeout) { done in + waitUntil(timeout: Timeout) { done in DispatchQueue.main.async { [safari] in let userAgent = SafariUserAgent(controller: safari!, callback: { result in expect(result).to(beSuccessful()) @@ -186,14 +186,14 @@ class SafariProviderSpec: QuickSpec { } } - it("should cancel the transaction when the user cancels the operation") { @MainActor in + it("should cancel the transaction when the user cancels the operation") { let transaction = SpyTransaction() TransactionStore.shared.store(transaction) userAgent.safariViewControllerDidFinish(safari) expect(transaction.isCancelled) == true } - it("should cancel the transaction when the user dismisses the safari view controller") { @MainActor in + it("should cancel the transaction when the user dismisses the safari view controller") { let transaction = SpyTransaction() TransactionStore.shared.store(transaction) userAgent.presentationControllerDidDismiss(safari.presentationController!) diff --git a/Auth0Tests/StubURLProtocol.swift b/Auth0Tests/StubURLProtocol.swift new file mode 100644 index 00000000..18a92d3e --- /dev/null +++ b/Auth0Tests/StubURLProtocol.swift @@ -0,0 +1,46 @@ +// +// StubURLProtocol.swift +// Auth0 +// +// Created by Desu Sai Venkat on 26/06/24. +// Copyright © 2024 Auth0. All rights reserved. +// + +import Foundation + +class StubURLProtocol: URLProtocol { + private var session: URLSession? + private var dataTask: URLSessionDataTask? + + override class func canInit(with request: URLRequest) -> Bool { + return NetworkStub.stubs.contains { $0.condition(request) } + } + + override class func canonicalRequest(for request: URLRequest) -> URLRequest { + return request + } + + override func startLoading() { + if let stub = NetworkStub.stubs.first(where: { $0.condition(request) }) { + let (data, response, error) = stub.response(request) + if let error = error { + client?.urlProtocol(self, didFailWithError: error) + } else { + if let response = response { + client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: .notAllowed) + } + if let data = data { + client?.urlProtocol(self, didLoad: data) + } + client?.urlProtocolDidFinishLoading(self) + } + } + } + + override func stopLoading() { + dataTask?.cancel() + session = nil + dataTask = nil + } +} + diff --git a/Auth0Tests/TelemetrySpec.swift b/Auth0Tests/TelemetrySpec.swift index bcfd8a5b..2d70443d 100644 --- a/Auth0Tests/TelemetrySpec.swift +++ b/Auth0Tests/TelemetrySpec.swift @@ -6,7 +6,7 @@ import Nimble class TelemetrySpec: QuickSpec { - override func spec() { + override class func spec() { var telemetry: Telemetry! @@ -57,6 +57,8 @@ class TelemetrySpec: QuickSpec { expect(env["tvOS"]).toNot(beNil()) #elseif os(watchOS) expect(env["watchOS"]).toNot(beNil()) + #elseif os(visionOS) + expect(env["visionOS"]).toNot(beNil()) #else expect(env["unknown"]).toNot(beNil()) #endif @@ -104,6 +106,8 @@ class TelemetrySpec: QuickSpec { expect(env["tvOS"]).toNot(beNil()) #elseif os(watchOS) expect(env["watchOS"]).toNot(beNil()) + #elseif os(visionOS) + expect(env["visionOS"]).toNot(beNil()) #else expect(env["unknown"]).toNot(beNil()) #endif @@ -113,15 +117,15 @@ class TelemetrySpec: QuickSpec { describe("telemetry header") { - var telemetry = Telemetry() - it("should set telemetry header") { + let telemetry = Telemetry() let request = NSMutableURLRequest() telemetry.addTelemetryHeader(request: request) expect(request.value(forHTTPHeaderField: "Auth0-Client")) == telemetry.value } it("should not set telemetry header when disabled") { + var telemetry = Telemetry() let request = NSMutableURLRequest() telemetry.enabled = false telemetry.addTelemetryHeader(request: request) diff --git a/Auth0Tests/TransactionStoreSpec.swift b/Auth0Tests/TransactionStoreSpec.swift index 25db4543..f9b35e75 100644 --- a/Auth0Tests/TransactionStoreSpec.swift +++ b/Auth0Tests/TransactionStoreSpec.swift @@ -6,7 +6,7 @@ import Nimble class TransactionStoreSpec: QuickSpec { - override func spec() { + override class func spec() { var storage: TransactionStore! var transaction: SpyTransaction! diff --git a/Auth0Tests/UserInfoSpec.swift b/Auth0Tests/UserInfoSpec.swift index 43afbc18..71203a02 100644 --- a/Auth0Tests/UserInfoSpec.swift +++ b/Auth0Tests/UserInfoSpec.swift @@ -8,7 +8,7 @@ import JWTDecode fileprivate let BasicProfileJWT = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhdXRoMHwxMjM0NTY3ODkiLCJuYW1lIjoic3VwcG9ydCIsIm5pY2tuYW1lIjoic3VwIiwicGljdHVyZSI6Imh0dHBzOi8vYXV0aDAuY29tL3BpY3R1cmUiLCJ1cGRhdGVkX2F0IjoiMTQ0MDAwNDY4MSJ9.TppFbhhG2or0Ygtig_7wvMWj5pj1nibZQKlhp6YA0NnEmAU5oj9KxkL9BGCAjIUQcImO3Suiur27qNRDvTY7yG61kUfVFYmcdCcYZ3tuS2glA2Ofwjv-gkgkORFaggqwT4jaZ19MViHtW71AjH-l8Q9HbbCfD3pCI-M-95oSs7sPssXw3vOMbC_iMm-0TPzwSs32rc2Rmpni3T-rjthb7ZjYxpm2RUPvlpUMev0nb_E3QbLG-ct8jWwvDAjZbTgCYBkw0pmp57T4VBQ8acTQGvOi1lryrJ6kK9O9a_h9Yxf1t4HhBhfMW6p7fXNLVMYo5su3NFqW1KMVgUW7jNzKwA" class UserInfoSpec: QuickSpec { - override func spec() { + override class func spec() { describe("init from json") { diff --git a/Auth0Tests/UserPatchAttributesSpec.swift b/Auth0Tests/UserPatchAttributesSpec.swift index bde9f891..56fa70b7 100644 --- a/Auth0Tests/UserPatchAttributesSpec.swift +++ b/Auth0Tests/UserPatchAttributesSpec.swift @@ -9,7 +9,7 @@ private let Password = "PASSWORD RANDOM" class UserPatchAttributesSpec: QuickSpec { - override func spec() { + override class func spec() { var attributes: UserPatchAttributes! beforeEach { diff --git a/Auth0Tests/UsersSpec.swift b/Auth0Tests/UsersSpec.swift index 6c2ee454..92fa4cbe 100644 --- a/Auth0Tests/UsersSpec.swift +++ b/Auth0Tests/UsersSpec.swift @@ -1,11 +1,6 @@ import Foundation import Quick import Nimble -import OHHTTPStubs -#if SWIFT_PACKAGE -import OHHTTPStubsSwift -#endif - @testable import Auth0 private let Domain = "samples.auth0.com" @@ -16,36 +11,33 @@ private let Provider = "facebook" class UsersSpec: QuickSpec { - override func spec() { + override class func spec() { let users = Auth0.users(token: Token, domain: Domain) beforeEach { - stub(condition: isHost(Domain)) { _ in catchAllResponse() }.name = "YOU SHALL NOT PASS!" + URLProtocol.registerClass(StubURLProtocol.self) } afterEach { - HTTPStubs.removeAllStubs() + NetworkStub.clearStubs() + URLProtocol.unregisterClass(StubURLProtocol.self) } describe("GET /users/:identifier") { beforeEach { - stub(condition: isUsersPath(Domain, identifier: UserId) && isMethodGET() && hasBearerToken(Token)) - { _ in apiSuccessResponse(json: ["user_id": UserId, "email": SupportAtAuth0]) } - .name = "User Fetch" - - stub(condition: isUsersPath(Domain, identifier: UserId) && isMethodGET() && hasQueryParameters(["fields": "user_id", "include_fields": "true"]) && hasBearerToken(Token)) - { _ in apiSuccessResponse(json: ["user_id": UserId]) } - .name = "User Fetch including fields" - - stub(condition: isUsersPath(Domain, identifier: UserId) && isMethodGET() && hasQueryParameters(["fields": "user_id", "include_fields": "false"]) && hasBearerToken(Token)) - { _ in apiSuccessResponse(json: ["email": SupportAtAuth0]) } - .name = "User Fetch excluding fields" + NetworkStub.addStub(condition: { + $0.isUsersPath(Domain, identifier: UserId) && $0.isMethodGET && $0.hasBearerToken(Token) && $0.hasQueryParameters(["fields": "user_id", "include_fields": "true"])}, response:apiSuccessResponse(json: ["user_id": UserId])) + NetworkStub.addStub(condition: { + $0.isUsersPath(Domain, identifier: UserId) && $0.isMethodGET && $0.hasBearerToken(Token) && $0.hasQueryParameters(["fields": "user_id", "include_fields": "false"])}, response:apiSuccessResponse(json: ["email": SupportAtAuth0])) + NetworkStub.addStub(condition: { + $0.isUsersPath(Domain, identifier: UserId) && $0.isMethodGET && $0.hasBearerToken(Token) }, response:apiSuccessResponse(json: ["user_id": UserId, "email": SupportAtAuth0])) } + it("should return single user by id") { - await waitUntil(timeout: Timeout) { done in + waitUntil(timeout: Timeout) { done in users.get(UserId).start { result in expect(result).to(haveObjectWithAttributes(["user_id", "email"])) done() @@ -54,7 +46,7 @@ class UsersSpec: QuickSpec { } it("should return specified user fields") { - await waitUntil(timeout: Timeout) { done in + waitUntil(timeout: Timeout) { done in users.get(UserId, fields: ["user_id"]).start { result in expect(result).to(haveObjectWithAttributes(["user_id"])) done() @@ -63,7 +55,7 @@ class UsersSpec: QuickSpec { } it("should exclude specified user fields") { - await waitUntil(timeout: Timeout) { done in + waitUntil(timeout: Timeout) { done in users.get(UserId, fields: ["user_id"], include: false).start { result in expect(result).to(haveObjectWithAttributes(["email"])) done() @@ -72,8 +64,9 @@ class UsersSpec: QuickSpec { } it("should fail request with management error") { - stub(condition: isUsersPath(Domain, identifier: NonExistentUser) && isMethodGET() && hasBearerToken(Token)) { _ in managementErrorResponse(error: "not_found", description: "not found user", code: "user_not_found", statusCode: 400)}.name = "user not found" - await waitUntil(timeout: Timeout) { done in + NetworkStub.addStub(condition: { + $0.isUsersPath(Domain, identifier: NonExistentUser) && $0.isMethodGET && $0.hasBearerToken(Token) }, response:managementErrorResponse(error: "not_found", description: "not found user", code: "user_not_found", statusCode: 400)) + waitUntil(timeout: Timeout) { done in users.get(NonExistentUser).start { result in expect(result).to(haveManagementError("not_found", description: "not found user", code: "user_not_found", statusCode: 400)) done() @@ -82,10 +75,9 @@ class UsersSpec: QuickSpec { } it("should fail request with generic error") { - stub(condition: isUsersPath(Domain, identifier: NonExistentUser) && isMethodGET() && hasBearerToken(Token)) { _ in - return apiFailureResponse(string: "foo", statusCode: 400) - }.name = "user not found" - await waitUntil(timeout: Timeout) { done in + NetworkStub.addStub(condition: { + $0.isUsersPath(Domain, identifier: NonExistentUser) && $0.isMethodGET && $0.hasBearerToken(Token) }, response:apiFailureResponse(string: "foo", statusCode: 400)) + waitUntil(timeout: Timeout) { done in users.get(NonExistentUser).start { result in expect(result).to(haveManagementError(description: "foo", statusCode: 400)) done() @@ -98,11 +90,10 @@ class UsersSpec: QuickSpec { describe("PATCH /users/:identifier") { it("should send attributes") { - stub(condition: isUsersPath(Domain, identifier: UserId) && isMethodPATCH() && hasAllOf(["username": Support, "connection": Provider]) && hasBearerToken(Token)) - { _ in apiSuccessResponse(json: ["user_id": UserId, "email": SupportAtAuth0, "username": Support, "connection": Provider]) } - .name = "User Patch" + NetworkStub.addStub(condition: { + $0.isUsersPath(Domain, identifier: UserId) && $0.isMethodPATCH && $0.hasAllOf(["username": Support, "connection": Provider]) && $0.hasBearerToken(Token) }, response:apiSuccessResponse(json: ["user_id": UserId, "email": SupportAtAuth0, "username": Support, "connection": Provider])) - await waitUntil(timeout: Timeout) { done in + waitUntil(timeout: Timeout) { done in let attributes = UserPatchAttributes().username(Support, connection: Provider) users.patch(UserId, attributes: attributes).start { result in expect(result).to(haveObjectWithAttributes(["user_id", "email", "username", "connection"])) @@ -113,11 +104,10 @@ class UsersSpec: QuickSpec { it("should patch userMetadata") { let metadata = ["role": "admin"] - stub(condition: isUsersPath(Domain, identifier: UserId) && isMethodPATCH() && hasUserMetadata(metadata) && hasBearerToken(Token)) - { _ in apiSuccessResponse(json: ["user_id": UserId, "email": SupportAtAuth0]) } - .name = "User Patch user_metadata" + NetworkStub.addStub(condition: { + $0.isUsersPath(Domain, identifier: UserId) && $0.isMethodPATCH && $0.hasUserMetadata(metadata) && $0.hasBearerToken(Token) }, response:apiSuccessResponse(json: ["user_id": UserId, "email": SupportAtAuth0])) - await waitUntil(timeout: Timeout) { done in + waitUntil(timeout: Timeout) { done in users.patch(UserId, userMetadata: metadata).start { result in expect(result).to(haveObjectWithAttributes(["user_id", "email"])) done() @@ -126,8 +116,9 @@ class UsersSpec: QuickSpec { } it("should fail request with management error") { - stub(condition: isUsersPath(Domain, identifier: NonExistentUser) && isMethodPATCH() && hasBearerToken(Token)) { _ in managementErrorResponse(error: "not_found", description: "not found user", code: "user_not_found", statusCode: 400)}.name = "user not found" - await waitUntil(timeout: Timeout) { done in + NetworkStub.addStub(condition: { + $0.isUsersPath(Domain, identifier: NonExistentUser) && $0.isMethodPATCH && $0.hasBearerToken(Token) }, response:managementErrorResponse(error: "not_found", description: "not found user", code: "user_not_found", statusCode: 400)) + waitUntil(timeout: Timeout) { done in users.patch(NonExistentUser, attributes: UserPatchAttributes().blocked(true)).start { result in expect(result).to(haveManagementError("not_found", description: "not found user", code: "user_not_found", statusCode: 400)) done() @@ -136,10 +127,9 @@ class UsersSpec: QuickSpec { } it("should fail request with generic error") { - stub(condition: isUsersPath(Domain, identifier: NonExistentUser) && isMethodPATCH() && hasBearerToken(Token)) { _ in - return apiFailureResponse(string: "foo", statusCode: 400) - }.name = "user not found" - await waitUntil(timeout: Timeout) { done in + NetworkStub.addStub(condition: { + $0.isUsersPath(Domain, identifier: NonExistentUser) && $0.isMethodPATCH && $0.hasBearerToken(Token) }, response: apiFailureResponse(string: "foo", statusCode: 400)) + waitUntil(timeout: Timeout) { done in users.patch(NonExistentUser, attributes: UserPatchAttributes().blocked(true)).start { result in expect(result).to(haveManagementError(description: "foo", statusCode: 400)) done() @@ -152,9 +142,11 @@ class UsersSpec: QuickSpec { describe("PATCH /users/:identifier/identities") { it("should link with just a token") { - stub(condition: isLinkPath(Domain, identifier: UserId) && isMethodPOST() && hasAllOf(["link_with": "token"]) && hasBearerToken(Token)) - { _ in apiSuccessResponse(jsonArray: [["user_id": UserId, "email": SupportAtAuth0]])}.name = "user linked" - await waitUntil(timeout: Timeout) { done in + + NetworkStub.addStub(condition: { + $0.isLinkPath(Domain, identifier: UserId) && $0.isMethodPOST && $0.hasAllOf(["link_with": "token"]) && $0.hasBearerToken(Token) } + , response: apiSuccessResponse(jsonArray: [["user_id": UserId, "email": SupportAtAuth0]])) + waitUntil(timeout: Timeout) { done in users.link(UserId, withOtherUserToken: "token").start { result in expect(result).to(beSuccessful()) done() @@ -163,9 +155,10 @@ class UsersSpec: QuickSpec { } it("should link without a token") { - stub(condition: isLinkPath(Domain, identifier: UserId) && isMethodPOST() && hasAllOf(["user_id": "other_id", "provider": Provider]) && hasBearerToken(Token)) - { _ in apiSuccessResponse(jsonArray: [["user_id": UserId, "email": SupportAtAuth0]])}.name = "user linked" - await waitUntil(timeout: Timeout) { done in + NetworkStub.addStub(condition: { + $0.isLinkPath(Domain, identifier: UserId) && $0.isMethodPOST && $0.hasAllOf(["user_id": "other_id", "provider": Provider]) && $0.hasBearerToken(Token) } + , response: apiSuccessResponse(jsonArray: [["user_id": UserId, "email": SupportAtAuth0]])) + waitUntil(timeout: Timeout) { done in users.link(UserId, withUser: "other_id", provider: Provider).start { result in expect(result).to(beSuccessful()) done() @@ -174,9 +167,10 @@ class UsersSpec: QuickSpec { } it("should link without a token specifying a connection id") { - stub(condition: isLinkPath(Domain, identifier: UserId) && isMethodPOST() && hasAllOf(["user_id": "other_id", "provider": Provider, "connection_id": "conn_1"]) && hasBearerToken(Token)) - { _ in apiSuccessResponse(jsonArray: [["user_id": UserId, "email": SupportAtAuth0]])}.name = "user linked" - await waitUntil(timeout: Timeout) { done in + NetworkStub.addStub(condition: { + $0.isLinkPath(Domain, identifier: UserId) && $0.isMethodPOST && $0.hasAllOf(["user_id": "other_id", "provider": Provider, "connection_id": "conn_1"]) && $0.hasBearerToken(Token) } + , response: apiSuccessResponse(jsonArray: [["user_id": UserId, "email": SupportAtAuth0]])) + waitUntil(timeout: Timeout) { done in users.link(UserId, withUser: "other_id", provider: Provider, connectionId: "conn_1").start { result in expect(result).to(beSuccessful()) done() @@ -185,9 +179,10 @@ class UsersSpec: QuickSpec { } it("should fail request with management error") { - stub(condition: isLinkPath(Domain, identifier: NonExistentUser) && isMethodPOST() && hasBearerToken(Token)) - { _ in managementErrorResponse(error: "not_found", description: "not found user", code: "user_not_found", statusCode: 400)}.name = "user not found" - await waitUntil(timeout: Timeout) { done in + NetworkStub.addStub(condition: { + $0.isLinkPath(Domain, identifier: NonExistentUser) && $0.isMethodPOST && $0.hasBearerToken(Token) } + ,response: managementErrorResponse(error: "not_found", description: "not found user", code: "user_not_found", statusCode: 400)) + waitUntil(timeout: Timeout) { done in users.link(NonExistentUser, withOtherUserToken: "token").start { result in expect(result).to(haveManagementError("not_found", description: "not found user", code: "user_not_found", statusCode: 400)) done() @@ -196,10 +191,10 @@ class UsersSpec: QuickSpec { } it("should fail request with generic error") { - stub(condition: isLinkPath(Domain, identifier: NonExistentUser) && isMethodPOST() && hasBearerToken(Token)) { _ in - return apiFailureResponse(string: "foo", statusCode: 400) - }.name = "user not found" - await waitUntil(timeout: Timeout) { done in + NetworkStub.addStub(condition: { + $0.isLinkPath(Domain, identifier: NonExistentUser) && $0.isMethodPOST && $0.hasBearerToken(Token) } + ,response: apiFailureResponse(string: "foo", statusCode: 400)) + waitUntil(timeout: Timeout) { done in users.link(NonExistentUser, withOtherUserToken: "token").start { result in expect(result).to(haveManagementError(description: "foo", statusCode: 400)) done() @@ -212,10 +207,10 @@ class UsersSpec: QuickSpec { describe("DELETE /users/:identifier/identities/:provider/:identityId") { it("should unlink") { - stub(condition: isUnlinkPath(Domain, identifier: "other_id", provider: Provider, identityId: UserId) && isMethodDELETE() && hasBearerToken(Token)) { _ in - return apiSuccessResponse(jsonArray: []) - }.name = "user unlinked" - await waitUntil(timeout: Timeout) { done in + NetworkStub.addStub(condition: { + $0.isUnlinkPath(Domain, identifier: "other_id", provider: Provider, identityId: UserId) && $0.isMethodDELETE && $0.hasBearerToken(Token) + }, response: apiSuccessResponse(jsonArray: [])) + waitUntil(timeout: Timeout) { done in users.unlink(identityId: UserId, provider: Provider, fromUserId: "other_id").start { result in expect(result).to(beSuccessful()) done() @@ -224,10 +219,10 @@ class UsersSpec: QuickSpec { } it("should fail request with management error") { - stub(condition: isUnlinkPath(Domain, identifier: "other_id", provider: Provider, identityId: NonExistentUser) && isMethodDELETE() && hasBearerToken(Token)) { _ in - return managementErrorResponse(error: "not_found", description: "not found user", code: "user_not_found", statusCode: 400) - }.name = "user not found" - await waitUntil(timeout: Timeout) { done in + NetworkStub.addStub(condition: { + $0.isUnlinkPath(Domain, identifier: "other_id", provider: Provider, identityId: NonExistentUser) && $0.isMethodDELETE && $0.hasBearerToken(Token) + }, response: managementErrorResponse(error: "not_found", description: "not found user", code: "user_not_found", statusCode: 400)) + waitUntil(timeout: Timeout) { done in users.unlink(identityId: NonExistentUser, provider: Provider, fromUserId: "other_id").start { result in expect(result).to(haveManagementError("not_found", description: "not found user", code: "user_not_found", statusCode: 400)) done() @@ -236,10 +231,10 @@ class UsersSpec: QuickSpec { } it("should fail request with generic error") { - stub(condition: isUnlinkPath(Domain, identifier: "other_id", provider: Provider, identityId: NonExistentUser) && isMethodDELETE() && hasBearerToken(Token)) { _ in - return apiFailureResponse(string: "foo", statusCode: 400) - }.name = "user not found" - await waitUntil(timeout: Timeout) { done in + NetworkStub.addStub(condition: { + $0.isUnlinkPath(Domain, identifier: "other_id", provider: Provider, identityId: NonExistentUser) && $0.isMethodDELETE && $0.hasBearerToken(Token) + }, response: apiFailureResponse(string: "foo", statusCode: 400)) + waitUntil(timeout: Timeout) { done in users.unlink(identityId: NonExistentUser, provider: Provider, fromUserId: "other_id").start { result in expect(result).to(haveManagementError(description: "foo", statusCode: 400)) done() diff --git a/Auth0Tests/WebAuthErrorSpec.swift b/Auth0Tests/WebAuthErrorSpec.swift index 782ae3ba..b67b7474 100644 --- a/Auth0Tests/WebAuthErrorSpec.swift +++ b/Auth0Tests/WebAuthErrorSpec.swift @@ -6,7 +6,7 @@ import Nimble class WebAuthErrorSpec: QuickSpec { - override func spec() { + override class func spec() { describe("init") { diff --git a/Auth0Tests/WebAuthSpec.swift b/Auth0Tests/WebAuthSpec.swift index e36dcda0..815056c7 100644 --- a/Auth0Tests/WebAuthSpec.swift +++ b/Auth0Tests/WebAuthSpec.swift @@ -16,31 +16,32 @@ extension URL { } } -private let ValidAuthorizeURLExample = "valid authorize url" - -class WebAuthSharedExamplesConfiguration: QuickConfiguration { - override class func configure(_ configuration: QCKConfiguration) { - sharedExamples(ValidAuthorizeURLExample) { (context: SharedExampleContext) in - let attrs = context() - let url = attrs["url"] as! URL - let params = attrs["query"] as! [String: String] - let domain = attrs["domain"] as! String - let components = url.a0_components - - it("should use domain \(domain)") { - expect(components?.scheme) == "https" - expect(components?.host) == String(domain.split(separator: "/").first!) - expect(components?.path).to(endWith("/authorize")) - } - - it("should have state parameter") { - expect(components?.queryItems).to(containItem(withName:"state")) - } - +class ValidAuthorizeURLBehavior: Behavior<[String:Any]> { + override class func spec(_ aContext: @escaping () -> [String : Any]) { + var context: [String:Any]! + var components: URLComponents! + + beforeEach { + context = aContext() + let url = context["url"] as! URL + components = url.a0_components + } + + it("should use domain") { + expect(components?.scheme) == "https" + let domain = context["domain"] as! String + expect(components?.host) == String(domain.split(separator: "/").first!) + expect(components?.path).to(endWith("/authorize")) + } + + it("should have state parameter") { + expect(components?.queryItems).to(containItem(withName:"state")) + } + + it("should have query parameters") { + let params = context["query"] as! [String: String] params.forEach { key, value in - it("should have query parameter \(key)") { - expect(components?.queryItems).to(containItem(withName: key, value: value)) - } + expect(components?.queryItems).to(containItem(withName: key, value: value)) } } } @@ -65,7 +66,7 @@ private let defaults = ["response_type": "code"] class WebAuthSpec: QuickSpec { - override func spec() { + override class func spec() { describe("init") { @@ -99,7 +100,7 @@ class WebAuthSpec: QuickSpec { describe("authorize URL") { - itBehavesLike(ValidAuthorizeURLExample) { + itBehavesLike(ValidAuthorizeURLBehavior.self) { return [ "url": newWebAuth() .buildAuthorizeURL(withRedirectURL: RedirectURL, defaults: defaults, state: State, organization: nil, invitation: nil), @@ -108,7 +109,7 @@ class WebAuthSpec: QuickSpec { ] } - itBehavesLike(ValidAuthorizeURLExample) { + itBehavesLike(ValidAuthorizeURLBehavior.self) { return [ "url": newWebAuth() .buildAuthorizeURL(withRedirectURL: RedirectURL, defaults: defaults, state: State, organization: nil, invitation: nil), @@ -117,7 +118,7 @@ class WebAuthSpec: QuickSpec { ] } - itBehavesLike(ValidAuthorizeURLExample) { + itBehavesLike(ValidAuthorizeURLBehavior.self) { return [ "url": newWebAuth() .buildAuthorizeURL(withRedirectURL: RedirectURL, defaults: defaults, state: State, organization: nil, invitation: nil), @@ -126,7 +127,7 @@ class WebAuthSpec: QuickSpec { ] } - itBehavesLike(ValidAuthorizeURLExample) { + itBehavesLike(ValidAuthorizeURLBehavior.self) { return [ "url": newWebAuth() .buildAuthorizeURL(withRedirectURL: RedirectURL, defaults: defaults, state: State, organization: nil, invitation: nil), @@ -135,7 +136,7 @@ class WebAuthSpec: QuickSpec { ] } - itBehavesLike(ValidAuthorizeURLExample) { + itBehavesLike(ValidAuthorizeURLBehavior.self) { return [ "url": newWebAuth() .buildAuthorizeURL(withRedirectURL: RedirectURL, defaults: defaults, state: State, organization: nil, invitation: nil), @@ -144,7 +145,7 @@ class WebAuthSpec: QuickSpec { ] } - itBehavesLike(ValidAuthorizeURLExample) { + itBehavesLike(ValidAuthorizeURLBehavior.self) { return [ "url": newWebAuth() .connection("facebook") @@ -154,7 +155,7 @@ class WebAuthSpec: QuickSpec { ] } - itBehavesLike(ValidAuthorizeURLExample) { + itBehavesLike(ValidAuthorizeURLBehavior.self) { let state = UUID().uuidString return [ "url": newWebAuth() @@ -165,7 +166,7 @@ class WebAuthSpec: QuickSpec { ] } - itBehavesLike(ValidAuthorizeURLExample) { + itBehavesLike(ValidAuthorizeURLBehavior.self) { let scope = "openid email phone" return [ "url": newWebAuth() @@ -176,7 +177,7 @@ class WebAuthSpec: QuickSpec { ] } - itBehavesLike(ValidAuthorizeURLExample) { + itBehavesLike(ValidAuthorizeURLBehavior.self) { let scope = "email phone" return [ "url": newWebAuth() @@ -187,7 +188,7 @@ class WebAuthSpec: QuickSpec { ] } - itBehavesLike(ValidAuthorizeURLExample) { + itBehavesLike(ValidAuthorizeURLBehavior.self) { return [ "url": newWebAuth() .maxAge(10000) // 1 second @@ -197,7 +198,7 @@ class WebAuthSpec: QuickSpec { ] } - itBehavesLike(ValidAuthorizeURLExample) { + itBehavesLike(ValidAuthorizeURLBehavior.self) { return [ "url": newWebAuth() .buildAuthorizeURL(withRedirectURL: RedirectURL, defaults: defaults, state: State, organization: "abc1234", invitation: nil), @@ -206,7 +207,7 @@ class WebAuthSpec: QuickSpec { ] } - itBehavesLike(ValidAuthorizeURLExample) { + itBehavesLike(ValidAuthorizeURLBehavior.self) { return [ "url": newWebAuth() .buildAuthorizeURL(withRedirectURL: RedirectURL, defaults: defaults, state: State, organization: "abc1234", invitation: "xyz6789"), @@ -215,7 +216,7 @@ class WebAuthSpec: QuickSpec { ] } - itBehavesLike(ValidAuthorizeURLExample) { + itBehavesLike(ValidAuthorizeURLBehavior.self) { var newDefaults = defaults newDefaults["audience"] = "https://wwww.google.com" return [ @@ -226,7 +227,7 @@ class WebAuthSpec: QuickSpec { ] } - itBehavesLike(ValidAuthorizeURLExample) { + itBehavesLike(ValidAuthorizeURLBehavior.self) { var newDefaults = defaults newDefaults["audience"] = "https://wwww.google.com" return [ @@ -238,7 +239,7 @@ class WebAuthSpec: QuickSpec { ] } - itBehavesLike(ValidAuthorizeURLExample) { + itBehavesLike(ValidAuthorizeURLBehavior.self) { return [ "url": newWebAuth() .audience("https://domain.auth0.com") @@ -248,7 +249,7 @@ class WebAuthSpec: QuickSpec { ] } - itBehavesLike(ValidAuthorizeURLExample) { + itBehavesLike(ValidAuthorizeURLBehavior.self) { return [ "url": newWebAuth() .connectionScope("user_friends,email") @@ -258,7 +259,7 @@ class WebAuthSpec: QuickSpec { ] } - itBehavesLike(ValidAuthorizeURLExample) { + itBehavesLike(ValidAuthorizeURLBehavior.self) { var newDefaults = defaults newDefaults["connection_scope"] = "email" return [ @@ -270,7 +271,7 @@ class WebAuthSpec: QuickSpec { ] } - itBehavesLike(ValidAuthorizeURLExample) { + itBehavesLike(ValidAuthorizeURLBehavior.self) { let organization = "foo" let invitation = "bar" let url = URL(string: "https://example.com?organization=\(organization)&invitation=\(invitation)")! @@ -283,7 +284,7 @@ class WebAuthSpec: QuickSpec { ] } - itBehavesLike(ValidAuthorizeURLExample) { + itBehavesLike(ValidAuthorizeURLBehavior.self) { return [ "url": newWebAuth() .authorizeURL(URL(string: "https://example.com/authorize")!) @@ -316,12 +317,14 @@ class WebAuthSpec: QuickSpec { #if os(iOS) platform = "ios" + #elseif os(visionOS) + platform = "visionos" #else platform = "macos" #endif #if compiler(>=5.10) - if #available(iOS 17.4, macOS 14.4, *) { + if #available(iOS 17.4, macOS 14.4, visionOS 1.2, *) { context("https") { it("should build with the domain") { expect(newWebAuth().useHTTPS().redirectURL?.absoluteString) == "https://\(Domain)/\(platform)/\(bundleId)/callback" @@ -482,7 +485,7 @@ class WebAuthSpec: QuickSpec { } - #if os(iOS) + #if os(iOS) || os(visionOS) describe("login") { var auth: Auth0WebAuth! @@ -499,7 +502,7 @@ class WebAuthSpec: QuickSpec { return SpyUserAgent() }) auth.start { _ in } - await expect(isStarted).toEventually(beTrue()) + expect(isStarted).toEventually(beTrue()) } it("should generate a state") { @@ -538,8 +541,8 @@ class WebAuthSpec: QuickSpec { return SpyUserAgent() }) auth.start { _ in } - await expect(redirectURL?.query).toEventually(contain("organization=foo")) - await expect(redirectURL?.query).toEventually(contain("invitation=bar")) + expect(redirectURL?.query).toEventually(contain("organization=foo")) + expect(redirectURL?.query).toEventually(contain("invitation=bar")) } it("should produce an invalid invitation URL error when the organization is missing") { @@ -548,7 +551,7 @@ class WebAuthSpec: QuickSpec { var result: WebAuthResult? _ = auth.invitationURL(URL(string: url)!) auth.start { result = $0 } - await expect(result).toEventually(haveWebAuthError(expectedError)) + expect(result).toEventually(haveWebAuthError(expectedError)) } it("should produce an invalid invitation URL error when the invitation is missing") { @@ -557,7 +560,7 @@ class WebAuthSpec: QuickSpec { var result: WebAuthResult? _ = auth.invitationURL(URL(string: url)!) auth.start { result = $0 } - await expect(result).toEventually(haveWebAuthError(expectedError)) + expect(result).toEventually(haveWebAuthError(expectedError)) } it("should produce an invalid invitation URL error when the organization and invitation are missing") { @@ -566,7 +569,7 @@ class WebAuthSpec: QuickSpec { var result: WebAuthResult? _ = auth.invitationURL(URL(string: url)!) auth.start { result = $0 } - await expect(result).toEventually(haveWebAuthError(expectedError)) + expect(result).toEventually(haveWebAuthError(expectedError)) } it("should produce an invalid invitation URL error when the query parameters are missing") { @@ -574,7 +577,7 @@ class WebAuthSpec: QuickSpec { var result: WebAuthResult? _ = auth.invitationURL(DomainURL) auth.start { result = $0 } - await expect(result).toEventually(haveWebAuthError(expectedError)) + expect(result).toEventually(haveWebAuthError(expectedError)) } it("should produce a no bundle identifier error when redirect URL is missing") { @@ -582,7 +585,7 @@ class WebAuthSpec: QuickSpec { var result: WebAuthResult? auth.redirectURL = nil auth.start { result = $0 } - await expect(result).toEventually(haveWebAuthError(expectedError)) + expect(result).toEventually(haveWebAuthError(expectedError)) } context("transaction") { @@ -591,13 +594,13 @@ class WebAuthSpec: QuickSpec { TransactionStore.shared.clear() } - it("should store a new transaction") { @MainActor in + it("should store a new transaction") { auth.start { _ in } expect(TransactionStore.shared.current).toNot(beNil()) TransactionStore.shared.cancel() } - it("should cancel the current transaction") { @MainActor in + it("should cancel the current transaction") { var result: WebAuthResult? auth.start { result = $0 } TransactionStore.shared.cancel() @@ -625,7 +628,7 @@ class WebAuthSpec: QuickSpec { return SpyUserAgent() }) auth.start { _ in } - await expect(isStarted).toEventually(beTrue()) + expect(isStarted).toEventually(beTrue()) } it("should not include the federated parameter by default") { @@ -635,7 +638,7 @@ class WebAuthSpec: QuickSpec { return SpyUserAgent() }) auth.clearSession() { _ in } - await expect(redirectURL?.query?.contains("federated")).toEventually(beFalse()) + expect(redirectURL?.query?.contains("federated")).toEventually(beFalse()) } it("should include the federated parameter") { @@ -645,7 +648,7 @@ class WebAuthSpec: QuickSpec { return SpyUserAgent() }) auth.clearSession(federated: true) { _ in } - await expect(redirectURL?.query?.contains("federated")).toEventually(beTrue()) + expect(redirectURL?.query?.contains("federated")).toEventually(beTrue()) } it("should produce a no bundle identifier error when redirect URL is missing") { @@ -664,19 +667,19 @@ class WebAuthSpec: QuickSpec { TransactionStore.shared.clear() } - it("should store a new transaction") { @MainActor in + it("should store a new transaction") { auth.clearSession() { _ in } expect(TransactionStore.shared.current).toNot(beNil()) } - it("should cancel the current transaction") { @MainActor in + it("should cancel the current transaction") { auth.clearSession() { result = $0 } TransactionStore.shared.cancel() expect(result).to(haveWebAuthError(WebAuthError(code: .userCancelled))) expect(TransactionStore.shared.current).to(beNil()) } - it("should resume the current transaction") { @MainActor in + it("should resume the current transaction") { auth.clearSession() { result = $0 } _ = TransactionStore.shared.resume(URL(string: "http://fake.com")!) expect(result).to(beSuccessful()) diff --git a/Auth0Tests/WebAuthSpies.swift b/Auth0Tests/WebAuthSpies.swift index e8ee5ff9..343d4178 100644 --- a/Auth0Tests/WebAuthSpies.swift +++ b/Auth0Tests/WebAuthSpies.swift @@ -49,7 +49,7 @@ class SpyTransaction: AuthTransaction { } -#if os(iOS) +#if os(iOS) || os(visionOS) import UIKit import SafariServices @@ -66,7 +66,9 @@ class SpyViewController: UIViewController { } } +#endif +#if os(iOS) class SpySafariViewController: SFSafariViewController { var isPresented = false diff --git a/Auth0Tests/WebAuthenticationSpec.swift b/Auth0Tests/WebAuthenticationSpec.swift index 1115b81a..28063785 100644 --- a/Auth0Tests/WebAuthenticationSpec.swift +++ b/Auth0Tests/WebAuthenticationSpec.swift @@ -6,7 +6,7 @@ import Nimble class WebAuthenticationSpec: QuickSpec { - override func spec() { + override class func spec() { let storage = TransactionStore.shared var transaction: SpyTransaction! diff --git a/Cartfile b/Cartfile index 50e8840d..e2cb0b01 100644 --- a/Cartfile +++ b/Cartfile @@ -1,2 +1,2 @@ -github "auth0/SimpleKeychain" ~> 1.1 -github "auth0/JWTDecode.swift" ~> 3.1 +github "auth0/SimpleKeychain" "1.2.0-beta.0" +github "auth0/JWTDecode.swift" "3.2.0-beta.0" diff --git a/Cartfile.private b/Cartfile.private index 26cccefe..3f5beb9b 100644 --- a/Cartfile.private +++ b/Cartfile.private @@ -1,3 +1,2 @@ -github "Quick/Quick" ~> 6.0 -github "Quick/Nimble" ~> 12.0 -github "asana/OHHTTPStubs" "9.1.0-asana" +github "Quick/Quick" ~> 7.0 +github "Quick/Nimble" ~> 13.0 diff --git a/Cartfile.resolved b/Cartfile.resolved index f1e2ac19..b371fe5f 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -1,5 +1,4 @@ -github "Quick/Nimble" "v12.0.1" -github "Quick/Quick" "v6.1.0" -github "asana/OHHTTPStubs" "9.1.0-asana" -github "auth0/JWTDecode.swift" "3.1.0" -github "auth0/SimpleKeychain" "1.1.0" +github "Quick/Nimble" "v13.3.0" +github "Quick/Quick" "v7.6.1" +github "auth0/JWTDecode.swift" "3.2.0-beta.0" +github "auth0/SimpleKeychain" "1.2.0-beta.0" diff --git a/Gemfile.lock b/Gemfile.lock index f1747a49..e07c995d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -15,27 +15,27 @@ GEM minitest (>= 5.1) mutex_m tzinfo (~> 2.0) - addressable (2.8.6) - public_suffix (>= 2.0.2, < 6.0) + addressable (2.8.7) + public_suffix (>= 2.0.2, < 7.0) algoliasearch (1.27.5) httpclient (~> 2.8, >= 2.8.3) json (>= 1.5.1) artifactory (3.0.17) atomos (0.1.3) aws-eventstream (1.3.0) - aws-partitions (1.944.0) - aws-sdk-core (3.197.0) + aws-partitions (1.950.0) + aws-sdk-core (3.201.0) aws-eventstream (~> 1, >= 1.3.0) aws-partitions (~> 1, >= 1.651.0) aws-sigv4 (~> 1.8) jmespath (~> 1, >= 1.6.1) - aws-sdk-kms (1.85.0) - aws-sdk-core (~> 3, >= 3.197.0) - aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.152.3) - aws-sdk-core (~> 3, >= 3.197.0) + aws-sdk-kms (1.88.0) + aws-sdk-core (~> 3, >= 3.201.0) + aws-sigv4 (~> 1.5) + aws-sdk-s3 (1.156.0) + aws-sdk-core (~> 3, >= 3.201.0) aws-sdk-kms (~> 1) - aws-sigv4 (~> 1.8) + aws-sigv4 (~> 1.5) aws-sigv4 (1.8.0) aws-eventstream (~> 1, >= 1.0.2) babosa (1.0.4) @@ -126,7 +126,7 @@ GEM faraday_middleware (1.2.0) faraday (~> 1.0) fastimage (2.3.1) - fastlane (2.221.0) + fastlane (2.221.1) CFPropertyList (>= 2.3, < 4.0.0) addressable (>= 2.8, < 3.0.0) artifactory (~> 3.0) @@ -222,7 +222,7 @@ GEM base64 mini_magick (4.13.1) mini_mime (1.1.5) - minitest (5.24.0) + minitest (5.24.1) molinillo (0.8.0) multi_json (1.15.0) multipart-post (2.4.1) @@ -263,7 +263,7 @@ GEM simctl (1.6.10) CFPropertyList naturally - slather (2.8.1) + slather (2.8.2) CFPropertyList (>= 2.2, < 4) activesupport clamp (~> 1.3) @@ -298,6 +298,7 @@ GEM xcpretty (~> 0.2, >= 0.0.7) PLATFORMS + arm64-darwin-22 arm64-darwin-23 x86_64-darwin-20 diff --git a/OAuth2Vision/ContentView.swift b/OAuth2Vision/ContentView.swift new file mode 100644 index 00000000..c3cd0d15 --- /dev/null +++ b/OAuth2Vision/ContentView.swift @@ -0,0 +1,101 @@ +// +// ContentView.swift +// OAuth2Vision +// +// Created by Desu Sai Venkat on 21/06/24. +// Copyright © 2024 Auth0. All rights reserved. +// + +import SwiftUI +import Auth0 + +struct ContentView: View { + @State private var credentialsText = "" + @State private var showCredentials = false + @State private var actionFailedMessage = "" + @State private var actionFailed = false + @State private var isAuthenticated = false + + let credentialsManager = CredentialsManager(authentication: Auth0.authentication()) + + var body: some View { + VStack { + if isAuthenticated { + Text("You are authenticated!") + Button(action: logout) { + Text("Logout") + .padding() + .background(Color.blue) + .foregroundColor(.white) + .cornerRadius(8) + } + + Button(action: getCredentials) { + Text("Show Credentials") + .padding() + .background(Color.blue) + .foregroundColor(.white) + .cornerRadius(8) + }.alert("Alert", isPresented: $showCredentials) { + Button("OK", role: .cancel) { } + } message: { + Text(credentialsText) + } + } else { + Button(action: startAuthentication) { + Text("Authenticate") + .padding() + .background(Color.blue) + .foregroundColor(.white) + .cornerRadius(8) + }.alert("Failed to login", isPresented: $actionFailed) { + Button("OK", role: .cancel) { } + } message: { + Text(actionFailedMessage) + } + } + } + .padding() + } + + private func startAuthentication() { + Auth0 + .webAuth() + .start { result in + switch result { + case .success(let credentials): + isAuthenticated = true + _ = credentialsManager.store(credentials: credentials) + case .failure(let error): + actionFailed = true + actionFailedMessage = "Failed with: \(error)" + } + } + } + + private func logout() { + Auth0 + .webAuth() + .clearSession { result in + switch result { + case .success: + isAuthenticated = false + print("Logged out") + case .failure(let error): + print("Failed with: \(error)") + } + } + } + + private func getCredentials() { + credentialsManager.credentials { result in + switch result { + case .success(let credentials): + showCredentials = true + credentialsText = "Obtained credentials: \(credentials)" + case .failure(let error): + print("Failed with: \(error)") + } + } + } +} diff --git a/OAuth2Vision/Info.plist b/OAuth2Vision/Info.plist new file mode 100644 index 00000000..20f75e2a --- /dev/null +++ b/OAuth2Vision/Info.plist @@ -0,0 +1,15 @@ + + + + + UIApplicationSceneManifest + + UIApplicationPreferredDefaultSceneSessionRole + UIWindowSceneSessionRoleApplication + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + + + diff --git a/OAuth2Vision/OAuth2VisionApp.swift b/OAuth2Vision/OAuth2VisionApp.swift new file mode 100644 index 00000000..7a5da13a --- /dev/null +++ b/OAuth2Vision/OAuth2VisionApp.swift @@ -0,0 +1,18 @@ +// +// OAuth2VisionApp.swift +// OAuth2Vision +// +// Created by Desu Sai Venkat on 21/06/24. +// Copyright © 2024 Auth0. All rights reserved. +// + +import SwiftUI + +@main +struct OAuth2VisionApp: App { + var body: some Scene { + WindowGroup { + ContentView() + } + } +} diff --git a/Package.swift b/Package.swift index 24b65120..5cd45c31 100644 --- a/Package.swift +++ b/Package.swift @@ -1,20 +1,19 @@ -// swift-tools-version:5.7 +// swift-tools-version:5.9 import PackageDescription -let webAuthPlatforms: [Platform] = [.iOS, .macOS, .macCatalyst] +let webAuthPlatforms: [Platform] = [.iOS, .macOS, .macCatalyst, .visionOS] let swiftSettings: [SwiftSetting] = [.define("WEB_AUTH_PLATFORM", .when(platforms: webAuthPlatforms))] let package = Package( name: "Auth0", - platforms: [.iOS(.v13), .macOS(.v11), .tvOS(.v13), .watchOS(.v7)], + platforms: [.iOS(.v14), .macOS(.v11), .tvOS(.v14), .watchOS(.v7), .visionOS(.v1)], products: [.library(name: "Auth0", targets: ["Auth0"])], dependencies: [ - .package(url: "https://github.com/auth0/SimpleKeychain.git", .upToNextMajor(from: "1.1.0")), - .package(url: "https://github.com/auth0/JWTDecode.swift.git", .upToNextMajor(from: "3.1.0")), - .package(url: "https://github.com/Quick/Quick.git", .upToNextMajor(from: "6.0.0")), - .package(url: "https://github.com/Quick/Nimble.git", .upToNextMajor(from: "12.0.0")), - .package(url: "https://github.com/AliSoftware/OHHTTPStubs.git", .upToNextMajor(from: "9.0.0")) + .package(url: "https://github.com/auth0/SimpleKeychain.git", exact:"1.2.0-beta.0"), + .package(url: "https://github.com/auth0/JWTDecode.swift.git", exact:"3.2.0-beta.0"), + .package(url: "https://github.com/Quick/Quick.git", .upToNextMajor(from: "7.0.0")), + .package(url: "https://github.com/Quick/Nimble.git", .upToNextMajor(from: "13.0.0")) ], targets: [ .target( @@ -32,8 +31,7 @@ let package = Package( dependencies: [ "Auth0", .product(name: "Quick", package: "Quick"), - .product(name: "Nimble", package: "Nimble"), - .product(name: "OHHTTPStubsSwift", package: "OHHTTPStubs") + .product(name: "Nimble", package: "Nimble") ], path: "Auth0Tests", exclude: ["Info.plist", "Auth0.plist"], diff --git a/README.md b/README.md index 2711c2b7..25b430d3 100644 --- a/README.md +++ b/README.md @@ -27,9 +27,9 @@ Migrating from v1? Check the [Migration Guide](V2_MIGRATION_GUIDE.md). ### Requirements -- iOS 13.0+ / macOS 11.0+ / tvOS 13.0+ / watchOS 7.0+ -- Xcode 14.x / 15.x -- Swift 5.7+ +- iOS 14.0+ / macOS 11.0+ / tvOS 14.0+ / watchOS 7.0+ +- Xcode 15.x +- Swift 5.9+ > [!IMPORTANT] > Check the [Support Policy](#support-policy) to learn when dropping Xcode, Swift, and platform versions will not be considered a **breaking change**. @@ -354,7 +354,9 @@ The minimum supported Swift minor version is the one released with the oldest-su ### Platforms -Once a platform version becomes unsupported, dropping it from Auth0.swift **will not be considered a breaking change**, and will be done in a **minor** release. For example, iOS 13 will cease to be supported when iOS 17 gets released, and Auth0.swift will be able to drop it in a minor release. +We support only the last four major versions of any platform, including the current major version. + +Once a platform version becomes unsupported, dropping it from Auth0.swift **will not be considered a breaking change**, and will be done in a **minor** release. For example, iOS 14 will cease to be supported when iOS 18 gets released, and Auth0.swift will be able to drop it in a minor release. In the case of macOS, the yearly named releases are considered a major platform version for the purposes of this Policy, regardless of the actual version numbers.